Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Topics - Slydog

#1
This will generate random, endless rolling hills.
It scrolls into/out of view at a specified speed.

Points that scroll off the screen (to the left) are removed from the array.
When a new hill is needed, it creates one (toggling the direction up or down).
It uses this new hill (really just a height value) and slices up the hill horizontally into segments.
It will scroll this new hill into view, then repeat endlessly.
In memory, the array should only have enough segment slices to fill the screen, plus a bit before, and the next hill yet to be shown.

Controls:
[<-] - Cursor Left - Decrease Speed (by 0.05)
[->] - Cursor Right - Increase Speed (by 0.05)
[1] - Change display mode to 'MODE_TEXTURE'
[2] - Change display mode to 'MODE_TEXTURE_WIRE'
[3] - Change display mode to 'MODE_WIRE'
[4] - Change display mode to 'MODE_POLYGON'
[5] - Change display mode to 'MODE_SOLID'
(The display modes are explained in the source code, nothing much really, just for debugging)

I'm not a graphics guy, so I just whipped up a sample texture to demo this.
If you notice the light brown bottom dirt border, and if you switch to 'MODE_TEXTURE_WIRE', the middle line follows this dirt border.
(This is configurable in code by a percentage - it is set to 25%)

While this may not affect the display too much, you can use the mid-line coordinates to wire up a physics engine and use that line for the colliders.
(It does affect the display a bit, kinda confusing, check out the code where ever I used that variable).

I tried commenting the code, but I ran out of time.  May update it later.
I attached the png texture I used.
And I zipped up the project too.
I hope it works, I'm in a rush!

Notes:
- a 'hill' is just the x/y coordinates of a peak or valley
- segments are how I slice up the hill and use a COSINE function to provide the rolling effect
- when the texture runs out, I just start it over again, but it may not have fully displayed the full texture, so when it joins with the next segment, it may not join up nicely - not sure how to get around this
- play with the hill settings, but if it is too much of a slope, the polygons will overlap, I could provide a work around, but it is simpler to just live with this constraint

RollingHills.gbas
Code (glbasic) Select
GLOBAL sx%, sy%
GLOBAL speed# = 4.0
GLOBAL draw_mode% = MODE_TEXTURE
GLOBAL sp_texture%

GLOBAL hills AS THillPoints
GLOBAL hills2 AS THillPoints
Main()
END

FUNCTION Main:
SYSTEMPOINTER TRUE
GETSCREENSIZE sx, sy
SEEDRND MID$(PLATFORMINFO$("time"), 17, 2)
LIMITFPS 60
IF NOT SETCURRENTDIR("Media")
DEBUG "Media Folder Not Found . . . Exiting!\n"
END
ENDIF


// Initialize 'hill' TYPE
sp_texture = GENSPRITE()
LOADSPRITE "grass.png", sp_texture // Load texture
hills.sp_texture = sp_texture
hills.x_width = 15 // How wide each section strip is, the wider, the more 'chunky' looking hills
GETSPRITESIZE sp_texture, hills.texture_width, hills.texture_height // Sprite size of the ground texture
hills.texture_ht_up = hills.texture_height * 0.75 // How tall to make the top portion
hills.texture_ht_dn = hills.texture_height * 0.25 // How tall to make the bottom portion (together should add to 1.0)
hills.x_change_min = 180 // When creating a new hill peak, it must be at LEAST this distance from previous
hills.x_change_max = 220 // When creating a new hill peak, it must be at MOST this distance from previous
hills.y_change_min = 50 // When creating a new hill peak, the peak must be at LEAST this much higher/lower
hills.y_change_max = 190 // When creating a new hill peak, the peak must be at MOST this much higher/lower
hills.y_min = sy * 0.40 // Keep hills below top 20% of screen
hills.y_max = sy * 0.95 // Keep hills above bottom 5% of screen
hills.rgb_bottom = RGB(100,60,20)
GenerateHills(hills) // Initial seeding of hills to fill screen at start

// Initialize 'hill2' TYPE
sp_texture = GENSPRITE()
LOADSPRITE "snow.png", sp_texture // Load texture
hills2.sp_texture = sp_texture
hills2.x_width = 25 // How wide each section strip is, the wider, the more 'chunky' looking hills
GETSPRITESIZE sp_texture, hills2.texture_width, hills2.texture_height // Sprite size of the ground texture
hills2.texture_ht_up = hills2.texture_height * 0.25 // How tall to make the top portion
hills2.texture_ht_dn = hills2.texture_height * 0.75 // How tall to make the bottom portion (together should add to 1.0)
hills2.x_change_min = 220 // When creating a new hill peak, it must be at LEAST this distance from previous
hills2.x_change_max = 280 // When creating a new hill peak, it must be at MOST this distance from previous
hills2.y_change_min = 70 // When creating a new hill peak, the peak must be at LEAST this much higher/lower
hills2.y_change_max = 220 // When creating a new hill peak, the peak must be at MOST this much higher/lower
hills2.y_min = sy * 0.15 // Keep hills below top 20% of screen
hills2.y_max = sy * 0.60 // Keep hills above bottom 5% of screen
hills2.rgb_bottom = RGB(30,45,60)
GenerateHills(hills2) // Initial seeding of hills to fill screen at start

// Main Loop
WHILE TRUE
// Speed: "<-" and "->" cursor to decrease / increase
IF KEY(203)
DEC speed, 0.05
IF speed < 0.1 THEN speed = 0.1
ELSEIF KEY(205)
INC speed, 0.05
IF speed > 20 THEN speed = 20
ENDIF

// Draw Mode: Good for see what is happening behind the scenes
LOCAL key_hit$
key_hit$ = INKEY$()
SELECT key_hit$
CASE "1"
draw_mode = MODE_TEXTURE
CASE "2"
draw_mode = MODE_TEXTURE_WIRE
CASE "3"
draw_mode = MODE_WIRE
CASE "4"
draw_mode = MODE_POLYGON
CASE "5"
draw_mode = MODE_SOLID
ENDSELECT

CLEARSCREEN RGB(0,100,220) // Blue background - azure
ALPHAMODE -1.0 // In case your texture has alpha values
SMOOTHSHADING FALSE // Don't pixelate the graphics
hills2.Draw(draw_mode) // Draw current hills using current draw mode
hills2.Scroll(speed*0.3) // Scroll the hills left based on speed - if needed, a new hill will be generated
hills.Draw(draw_mode) // Draw current hills using current draw mode
hills.Scroll(speed) // Scroll the hills left based on speed - if needed, a new hill will be generated
SHOWSCREEN
WEND
ENDFUNCTION

FUNCTION GenerateHills: hill AS THillPoints
hill.Clear()

WHILE hill.hill_next.x < (sx + hill.x_change_max)
hill.GenerateNewHill()
WEND
ENDFUNCTION


types.gbas
Code (glbasic) Select
CONSTANT MODE_TEXTURE% = 1 // Normal textured display
CONSTANT MODE_TEXTURE_WIRE% = 2 // Wire outline overlayed on textured display
CONSTANT MODE_WIRE% = 3 // Wire outine of all calculated vectors
CONSTANT MODE_SOLID% = 4 // Fills in quads will solid colour - kinda neat when fast!
CONSTANT MODE_POLYGON% = 5 // Same as 'SOLID', except shades each polygon to see each polygon

CONSTANT PI = 3.14159

// General vector routine, makes other code cleaner
TYPE TVector
x#
y#

FUNCTION Set: x#, y#
self.x = x
self.y = y
ENDFUNCTION

FUNCTION Copy: v AS TVector
self.x = v.x
self.y = v.y
ENDFUNCTION

FUNCTION Scale: amount#
self.x = self.x * amount
self.y = self.y * amount
ENDFUNCTION

FUNCTION AverageWith AS TVector: v AS TVector
LOCAL rv AS TVector
rv.x = (self.x + v.x) / 2.0
rv.y = (self.y + v.y) / 2.0
RETURN rv
ENDFUNCTION

// Calc the magnitude / length of a vector
FUNCTION Magnitude#:
   RETURN SQR((self.x * self.x) + (self.y * self.y))
ENDFUNCTION
ENDTYPE

// A hill point is a point along the curved hill, a new one is generated every 'self.x_width' pixels
TYPE THillPoint
pu AS TVector // Higher point - used for top portion of texture (grass)
pm AS TVector // Base/mid point - good to use for physics engine as the 'contact' location
pd AS TVector // Lower point - used for the bottom portion of texture (dirt border)
texture_x# // How far along texture this point is
ENDTYPE

TYPE THillPoints
points[] AS THillPoint // All of the points along all of the hills
x_width% // How far between curve segments (the more, the smoother the curve)
texture_height# // How tall the ground texture is
texture_ht_up# // How tall the ground texture is - above the base point
texture_ht_dn# // How tall the ground texture is - below the base point
texture_width# // How wide the ground texture is
y_min% // How high up the screen a hill peak can reach
y_max% // How low on the screen a hill valley can reach
x_change_min% // New hill peaks/valleys must be at least this 'x' distance from previous
x_change_max% // New hill peaks/valleys must be at most this 'x' distance from previous
y_change_min% // New hills must be at least this far above or below
y_change_max% // New hills must be at most this far above or below
peak_valley_toggle% // Remember if previous hill was a peak or a valley, toggle each time
sp_texture% // Sprite ID of hill top texture
rgb_bottom% // Colour below the curve and texture (the bottom dirt colour)

hill_prev AS TVector // Used when calculating new hills
hill_next AS TVector // The current hill vector will be stored here

// Used to clear / initialize the data
FUNCTION Clear:
LOCAL y_adjust% = 30 // Used to make sure we don't start too high or too low
DIM self.points[0] // Clear previous points
self.hill_next.Set(0, RND(self.y_max - self.y_min - (y_adjust * 2)) + self.y_min + y_adjust) // Initialize 'hill_next' at a random valid 'y' pos
self.hill_prev.Set(0, 0) // Initialize 'hill_prev'
ENDFUNCTION

// Draw current hill points - various draw modes are available
FUNCTION Draw: mode% = MODE_TEXTURE
LOCAL hx%
LOCAL xy[] AS TVector
LOCAL uv[] AS TVector

SELECT mode

CASE MODE_WIRE
FOR hx = 1 TO LEN(self.points[]) - 1
DRAWLINE self.points[hx-1].pu.x, self.points[hx-1].pu.y, self.points[hx].pu.x, self.points[hx].pu.y, RGB(255,255,255)
DRAWLINE self.points[hx-1].pm.x, self.points[hx-1].pm.y, self.points[hx].pm.x, self.points[hx].pm.y, RGB(255,255,255)
DRAWLINE self.points[hx-1].pd.x, self.points[hx-1].pd.y, self.points[hx].pd.x, self.points[hx].pd.y, RGB(255,255,255)
DRAWLINE self.points[hx].pu.x,   self.points[hx].pu.y,   self.points[hx].pd.x, self.points[hx].pd.y, RGB(255,255,255)
NEXT

CASE MODE_POLYGON
STARTPOLY -1, 2
FOR hx = 1 TO LEN(self.points[]) - 1
PolyQuick(self.points[hx-1].pu, self.points[hx].pu, self.points[hx-1].pd, self.points[hx].pd, RGB(250,175,0), RGB(175,120,0))
NEXT
ENDPOLY

CASE MODE_SOLID
STARTPOLY -1, 2
FOR hx = 1 TO LEN(self.points[]) - 1
PolyQuick(self.points[hx-1].pu, self.points[hx].pu, self.points[hx-1].pd, self.points[hx].pd, RGB(250,180,0), RGB(250,180,0))
NEXT
ENDPOLY

CASE MODE_TEXTURE
DIM xy[2]
DIM uv[4]
xy[0].y = sy
xy[1].y = sy

// Bottom area first (dirt?)
STARTPOLY -1, 2
FOR hx = 1 TO LEN(self.points[]) - 1
IF self.points[hx].pm.x < sx + (self.x_width*2)
xy[0].x = self.points[hx-1].pd.x
xy[1].x = self.points[hx].pd.x
PolyQuick(self.points[hx-1].pm, self.points[hx].pm, xy[0], xy[1], self.rgb_bottom, self.rgb_bottom)
ENDIF
NEXT
ENDPOLY

// Now draw hills
uv[0].y = 0
uv[1].y = 0
uv[2].y = self.texture_height
uv[3].y = self.texture_height
STARTPOLY self.sp_texture, 2
FOR hx = 1 TO LEN(self.points[]) - 1
IF self.points[hx].pm.x < sx + (self.x_width*2)
uv[0].x = self.points[hx-1].texture_x
uv[1].x = self.points[hx].texture_x
IF uv[0].x = self.texture_width THEN uv[0].x = 0
IF uv[1].x < uv[0].x
uv[1].x = uv[0].x + self.x_width
ENDIF
uv[2].x = uv[0].x
uv[3].x = uv[1].x
PolyDraw(self.points[hx-1].pu, self.points[hx].pu, self.points[hx-1].pd, self.points[hx].pd, uv[0], uv[1], uv[2], uv[3])
ENDIF
NEXT
ENDPOLY

CASE MODE_TEXTURE_WIRE
self.Draw(MODE_TEXTURE) // First draw in texture mode
self.Draw(MODE_WIRE) // Then overlay with wire mode

ENDSELECT
ENDFUNCTION

FUNCTION Scroll: x#
FOREACH p IN self.points[]
DEC p.pu.x, x#
DEC p.pm.x, x#
DEC p.pd.x, x#
IF (p.pu.x < -(self.x_width*2)) AND (p.pd.x < -(self.x_width*2)) THEN DELETE p // Delete point if too far to the left offscreen
NEXT
DEC self.hill_prev.x, x#
DEC self.hill_next.x, x#

// Generate new hill yet?
IF self.hill_next.x < (sx + (self.x_width * 2)) THEN self.GenerateNewHill()
ENDFUNCTION

FUNCTION GenerateNewHill:
LOCAL x_dist%
ALIAS h0 AS self.hill_prev
ALIAS h1 AS self.hill_next

h0.Copy(h1) // Move old 'next' to 'prev'

// Random 'x' distance from previous peak
x_dist = self.x_change_min + RND(self.x_change_max - self.x_change_min)
x_dist = (x_dist / self.x_width) * self.x_width // Snap to 'hillPoints.x_width'
h1.x = h0.x + x_dist

// Random 'y' distance from previous peak
IF self.peak_valley_toggle = TRUE
// New point will be below previous (ie: a 'valley')
h1.y = h0.y + (self.y_change_min + RND(self.y_change_max - self.y_change_min))
ELSE
// New point will be above previous (ie: a 'peak')
h1.y = h0.y - (self.y_change_min + RND(self.y_change_max - self.y_change_min))
ENDIF

// Adjust 'y' to keep in range
IF h1.y < (self.y_min + self.texture_ht_up) THEN h1.y = (self.y_min + self.texture_ht_up)
IF h1.y > (self.y_max - self.texture_ht_dn) THEN h1.y = (self.y_max - self.texture_ht_dn)

// Toggle peak / valley bit for next entry
self.peak_valley_toggle = NOT self.peak_valley_toggle

self.FillCurve()
ENDFUNCTION

// Fill in curve between these two 'hillPeaks' entries
FUNCTION FillCurve:
LOCAL segments#, sx#
LOCAL dx#, da#, ymid#, ampl#
LOCAL hillPoint AS THillPoint
LOCAL v_delta AS TVector
LOCAL partial#, overage#
LOCAL length#
ALIAS h0 AS self.hill_prev
ALIAS h1 AS self.hill_next
segments = (h1.x - h0.x) / self.x_width

dx = (h1.x - h0.x) / segments
da =  PI / segments
ymid = (h0.y + h1.y) / 2.0
ampl = (h0.y - h1.y) / 2.0

FOR sx = 0 TO segments-1
hillPoint.pm.x = h0.x + sx*dx
hillPoint.pm.y = ymid + ampl * COS(sx*da * 180.0/PI)
IF LEN(self.points[]) = 0
hillPoint.texture_x = 0
ELSE
v_delta.Set(hillPoint.pm.x - self.points[-1].pm.x, hillPoint.pm.y - self.points[-1].pm.y)
length = v_delta.Magnitude()
hillPoint.texture_x = self.points[-1].texture_x + length
// Are we at the end of the texture, time to start at the beginning again
IF hillPoint.texture_x > self.texture_width
overage = hillPoint.texture_x - self.texture_width // How far over the end of the texture are we?
partial = overage / length // As a percentage (between 0 and 1)
// If it's really small, just stretch it to tne end of the texture
IF partial < 0.1
hillPoint.texture_x = self.texture_width
// If it's really large, snap the previous point to the end of the texture, and start this entry over
ELSEIF partial > 0.9
hillPoint.texture_x = length
self.points[-1].texture_x = self.texture_width
// Somewhere in between?  Then create a mid-point entry by splitting this polygon in two
ELSE
// Mid point entry
hillPoint.texture_x = self.texture_width
hillPoint.pm.x = h0.x + (sx-partial)*dx
hillPoint.pm.y = ymid + ampl * COS((sx-partial)*da * 180.0/PI)
self.AddPoint(hillPoint)
// Regular entry
hillPoint.texture_x = overage
hillPoint.pm.x = h0.x + sx*dx
hillPoint.pm.y = ymid + ampl * COS(sx*da * 180.0/PI)
ENDIF
ENDIF
ENDIF
self.AddPoint(hillPoint)
    NEXT
ENDFUNCTION

FUNCTION AddPoint: h AS THillPoint
DIMPUSH self.points[], h

IF LEN(self.points[]) = 1
self.points[0].pu.Set(0, self.points[0].pm.y - self.texture_ht_up)
self.points[0].pd.Set(0, self.points[0].pm.y + self.texture_ht_dn)
RETURN
ENDIF

ALIAS h0 AS self.points[-2]
ALIAS h1 AS self.points[-1]
LOCAL angle#
LOCAL v1 AS TVector
LOCAL v0 AS TVector

// Calculate 'p2' of HillPoint - form rectangle with previous point
angle = ATAN(h1.pm.y - h0.pm.y, h1.pm.x - h0.pm.x)  -90// Angle of the two points

v1.x = COS(angle) * self.texture_ht_up + h1.pm.x
v1.y = SIN(angle) * self.texture_ht_up + h1.pm.y
v0.x = COS(angle) * self.texture_ht_up + h0.pm.x
v0.y = SIN(angle) * self.texture_ht_up + h0.pm.y
h0.pu = h0.pu.AverageWith(v0)
h1.pu.Copy(v1)

v1.x = COS(angle) * -self.texture_ht_dn + h1.pm.x
v1.y = SIN(angle) * -self.texture_ht_dn + h1.pm.y
v0.x = COS(angle) * -self.texture_ht_dn + h0.pm.x
v0.y = SIN(angle) * -self.texture_ht_dn + h0.pm.y
h0.pd = h0.pd.AverageWith(v0)
h1.pd.Copy(v1)
ENDFUNCTION
ENDTYPE

FUNCTION PolyQuick: v1 AS TVector, v2 AS TVector, v3 AS TVector, v4 AS TVector, rgb1%, rgb2%
POLYNEWSTRIP
POLYVECTOR v1.x, v1.y, 0, 0, rgb1 // TL
POLYVECTOR v3.x, v3.y, 0, 1, rgb1 // BL
POLYVECTOR v2.x, v2.y, 1, 0, rgb2 // TR
POLYVECTOR v4.x, v4.y, 1, 1, rgb2 // BR
ENDFUNCTION


FUNCTION PolyDraw: v1 AS TVector, v2 AS TVector, v3 AS TVector, v4 AS TVector,  uv1 AS TVector, uv2 AS TVector, uv3 AS TVector, uv4 AS TVector
LOCAL rgb1% = RGB(255,255,255)
POLYNEWSTRIP
POLYVECTOR v1.x, v1.y, uv1.x, uv1.y, rgb1 // TL
POLYVECTOR v3.x, v3.y, uv3.x, uv3.y, rgb1 // BL
POLYVECTOR v2.x, v2.y, uv2.x, uv2.y, rgb1 // TR
POLYVECTOR v4.x, v4.y, uv4.x, uv4.y, rgb1 // BR
ENDFUNCTION


[EDIT] Fixed most / all bugs.  Updated demo for parallax.  See post below for details.

[attachment deleted by admin]
#2
Here's a quick dictionary / hash library I created when first starting with GLBasic.
I have never used it (changed my mind!), and I don't think it was ever tested.
It was created for simplicity, and perhaps should not be used too much in your main game loop.
I haven't tested it so I don't know if it works, or how fast it is:

Oh, and it only works for storing numerical values (floats) against a string. 
It could easily be update for strings.
Code (glbasic) Select
//==============================================================================
//  D I C T I O N A R Y
//==============================================================================
TYPE TDictionaryItem
name$
value
ENDTYPE

TYPE TDictionary
table[] AS TDictionaryItem

FUNCTION Clear:
DIM self.table[0]
ENDFUNCTION

FUNCTION Add: name$, value
// If 'name$' already exists, call 'Set' instead
IF self.Exists(name$)
self.Set(name$, value)
RETURN
ENDIF

LOCAL item AS TDictionaryItem
item.name$ = name$
item.value = value
DIMPUSH self.table[], item
ENDFUNCTION

FUNCTION Get: name$, defaultValue=-1
LOCAL rv# = defaultValue

FOREACH item IN self.table[]
IF item.name$ = name$
rv = item.value
BREAK
ENDIF
NEXT

RETURN rv
ENDFUNCTION

FUNCTION Set%: name$, value
// If the 'name$' doesn't exist in the table, call 'Add' instead
IF NOT self.Exists(name$)
self.Add(name$, value)
RETURN
ENDIF

FOREACH item IN self.table[]
IF item.name$ = name$
item.value = value
BREAK
ENDIF
NEXT
ENDFUNCTION

FUNCTION Exists%: name$
FOREACH item IN self.table[]
IF item.name$ = name$
RETURN TRUE
ENDIF
NEXT
RETURN FALSE
ENDFUNCTION

ENDTYPE


Usage:
Code (glbasic) Select

GLOBAL salaries AS TDictionary
salaries.Clear()
salaries.Add("Peter", 49000)
salaries.Add("Paul", 58000)
salaries.Add("Mary", 63000)
DEBUG "Paul makes $" + salaries.Get("Paul") + " per year\n"

salaries.Set("Paul", 67000)
DEBUG "Paul now makes $" + salaries.Get("Paul") + " per year\n"

IF NOT salaries.Exists("Slydog") THEN DEBUG "Slydog doesn't exist in employee table"

#3
Dell Canada (maybe US or other countries also?) has the ST2220T Multi-touch monitor on sale for the day (the '12 Days of Dell' promotion):

http://accessories.dell.com/sna/products/Monitors_Flat_Panel_Widescreen/productdetail.aspx?c=ca&l=en&s=dhs&cs=cadhs1&sku=320-1819

Does anybody know if we could use this with GLBasic, and that GLBasic will pick up multiple mice / inputs?
If so, this would be great for testing multi-touch for tablet and phone games without having to install on the device each time. 
(It is supposed to work with the iOS simulator, but the simulator doesn't work with GLB)

Windows 7 has touch / multi-touch built in, but not sure of the limitations, it may be gesture only (pinch, two finger swipe, etc) and not report multiple 'mice' to the applications.

Also, a company I buy from frequently, has a keyboard with a multi-touch panel on sale, and was wondering the same question, if it would work with GLBasic:
http://www.ncix.ca/products/index.php?sku=65580&vpn=E2700-Blk&manufacture=Rapoo%20Electronics&promoid=1311

Thanks!
#4
I noticed in the latest logfile:
Code (glbasic) Select
// 10.090
   // Compiler:
   //    Can assign types when declaring:
   //      LOCAL foo as Tfoo = bar


Are we closer to be able to use this in FUNCTION parameters for specifying a default for TYPE variables, such as:
Code (glbasic) Select
GLOBAL fontDefault AS TFont
GLOBAL fontHeading AS TFont

FontPrint "Using the default font", 0, 0
FontPrint "This is for a heading", 0, 100, fontHeading

FUNCTION FontPrint: text$, x%, y%, font AS TFont = fontDefault
    ....
ENDFUNCTION


If not, it would be nice to have optional TYPE parameters by using a 'NULL' concept such as:
Code (glbasic) Select
FUNCTION FontPrint: text$, x%, y%, font AS TFont = NULL
    IF font = NULL THEN font = fontDefault
    ....
ENDFUNCTION
#5
I created a dynamic 3d object in code for my game's title. 
(It's made from my maze drawing routines to look like a maze in the shape of my title)

I want this title model to move around (maybe based on touch or device tilting) the 3d world space.
It will move left / right / up / down, plus zoom in and out, rotate, etc (not decided exactly what yet).
Basically it will wander around inside a virtual 3d box.

Is there a way to tell if the entire model is within the view range (ie, can you see it)?
That way I can detect when it goes a bit out of view and change the direction to 'bounce' it back into view.
I can hard code this perhaps with trial and error, but constantly rotating it changes it's x/y screen edge boundaries so I wont know precisely when it goes off screen.

Here's a screen of how it looks now, it's tilted a bit to show what I mean.

[attachment deleted by admin]
#6
Is the current method of pointing the camera limited to a 'look at' location point?

My problem is when the player wants to turn around 180°.
Using the 'look at' method and interpolating the old location to the new location, the look point passes directly through the player creating a weird effect.  What I would like is to have the camera rotate around the player, so the 'look at' point path is circular.

I've been working on a custom camera system that lets you specify the camera rotation by using a y-axis rotation (yaw) and an x-axis rotation (pitch).  I'm keeping track of two variables (custom TYPE), 'look_cur' and 'look_dst', and each frame I adjust the camera rotation until it reaches the destination.  The rotation speed for yaw and pitch are specified also.

So far the yaw works great and I can now rotate around a point by specifying an angle.
I'm having a b**ch of a time with the pitch!   :blink:
I'm not even (yet?) considering adding 'roll'!

Could perhaps this feature be added directly to GLBasic as an alternative camera looking option?
You could simply specify the yaw, pitch, roll and the camera will point in that direction.

How, I don't know.  Currently I'm using SIN / COS.  Quaternions or matrices would be better I'm sure, but out of my scope.
If not, no problem.  I'll keep at it until it works.
#7
I needed a break from my game, so I thought I'd contribute something.

Programming with so many languages, I hate having to learn each language's methods of doing things, such as file handling.
What I like to do is wrap those commands in a common wrapper, so my code appears similar across the different languages.

Here's my attempt at simplifying the file handling commands of GLB.
It only covers the basic types of file commands people need.

Code (glbasic) Select
CONSTANT FILE_MODE_APPEND = -1
CONSTANT FILE_MODE_WRITE = 0
CONSTANT FILE_MODE_READ = 1

CONSTANT EOF$ = "<EOF>"

TYPE TFile
fh% // File Handle
name$ // File Name
data$ // File Contents
eof% // File is at end?

// Reads entire file into 'self.data$'.  Closes file when finished.
// 'name$' > Optional.  Will use previously set 'self.name$' if blank.
// Returns > [TRUE if succeed | FALSE if fail]
FUNCTION Get%: name$=""
IF name$ <> "" THEN self.name$ = name$
self.data$ = ""
LOCAL line$ = ""
IF self.Open(FILE_MODE_READ)
WHILE ENDOFFILE(self.fh) = FALSE
READLINE self.fh, line$
INC self.data$, line$
WEND
CLOSEFILE self.fh
ELSE
LOG(">FIL:{TFile.Get$      } *** Error Getting File: [" + self.name$ + "] ***")
RETURN FALSE
ENDIF
self.Close()
RETURN TRUE
ENDFUNCTION

// Reads the next line in an already opened file.  Closes file when end is reached.
// Returns > [line data if succeed | 'EOF$' if fail]
FUNCTION GetLine$:
LOCAL line$
IF ENDOFFILE(self.fh)
line$ = EOF$
self.eof = TRUE
self.Close()
ELSE
self.eof = FALSE
READLINE self.fh, line$
ENDIF
RETURN line$
ENDFUNCTION

// Writes 'self.data$' to file
// 'name$' > Optional.  Will use previously set 'self.name$' if blank.
// Returns > [TRUE if succeed | FALSE if fail]
FUNCTION Write%: name$=""
IF name$ <> "" THEN self.name$ = name$
IF self.Open(FILE_MODE_WRITE)
WRITESTR self.fh, self.data$
ELSE
LOG(">FIL:{TFile.Write$    } *** Error Writting File: [" + self.name$ + "] ***")
RETURN FALSE
ENDIF
self.Close()
RETURN TRUE
ENDFUNCTION

// Appends string to end of file.
// 'extra$'> Data to append
// 'name$' > Optional.  Will use previously set 'self.name$' if blank.
// Returns > [TRUE if succeed | FALSE if fail]
FUNCTION Append%: extra$, name$=""
IF name$ <> "" THEN self.name$ = name$
IF self.Open(FILE_MODE_APPEND)
INC self.data$, extra$
WRITESTR self.fh, extra$
ELSE
LOG(">FIL:{TFile.Append$   } *** Error Appending File: [" + extra$ + "] ***")
RETURN FALSE
ENDIF
self.Close()
RETURN TRUE
ENDFUNCTION

// Opens file for procesing.
// 'mode%' > [FILE_MODE_APPEND | FILE_MODE_WRITE | FILE_MODE_READ]
// 'name$' > Optional.  Will use previously set 'self.name$' if blank.
// Returns > [TRUE if succeed | FALSE if fail]
FUNCTION Open%: mode%, name$=""
IF name$ <> "" THEN self.name$ = name$
self.fh = GENFILE()
self.eof = FALSE
IF OPENFILE(self.fh, self.name$, mode) = FALSE
LOG(">FIL:{TFile.Open      } *** Error Opening File: [" + self.name$ + "] ***")
self.eof = TRUE
RETURN FALSE
ENDIF
RETURN TRUE
ENDFUNCTION

// Closes file to further processing
FUNCTION Close%:
IF self.fh >= 0 THEN CLOSEFILE self.fh
self.fh = -1
self.eof = TRUE
ENDFUNCTION

// Checks if file exists
// Returns > [TRUE if exists | FALSE if doesn't]
FUNCTION Exists%:
RETURN DOESFILEEXIST(self.name$)
ENDFUNCTION

// Returns file name portion from a longer file path
// eg. For a 'self.name$' of [C:/Programs/GLBasic/glbasic.exe] will return [glbasic.exe]
// 'slash$' > Optional.  Folder Seperator.  Use if your path uses '\' instead. Uses '/' as default
FUNCTION GetName$: slash$="/"
LOCAL slash%
slash = REVINSTR(self.name$, slash$)
IF slash <=0 THEN RETURN self.name$
RETURN MID$(self.name$, slash+1)
ENDFUNCTION

ENDTYPE


FUNCTION LOG: message$, add_linefeed% = TRUE, show_time% = TRUE
STATIC datetime% = -1 // Log timer
LOCAL elapsed# // How much time has elapsed since program start (or manual reset)
IF datetime < 0 THEN datetime = GETTIMERALL() // Initialize log timer if not already set
IF message$ = "RESET" THEN datetime = GETTIMERALL() // Manually reset log timer
elapsed = (GETTIMERALL() - datetime) / 1000.0
?IFDEF WIN32
//?IFDEF GLB_DEBUG
//?ENDIF
IF show_time = TRUE THEN DEBUG "[" + FORMAT$(8, 3, elapsed) + "] "
DEBUG message$
IF add_linefeed THEN DEBUG "\n"
?ELSE
INLINE
STDOUT(message_Str);
ENDINLINE
?ENDIF
ENDFUNCTION


Here's some basic usages examples:

Code (glbasic) Select
LOCAL file AS TFile
LOCAL line$

// Read settings
IF NOT file.Get("settings.ini") THEN RETURN FALSE
DEBUG "Data:" + file.data$

// Write new settings data
file.data$ = "abcdefghijklmnopqrstuvwxyz"
file.Write("settings.ini")
file.Append("ZYXWVUTSR", "settings.ini"

// Optional method by setting file name directly
file.name$ = "scores.txt"
IF NOT file.Exists() THEN RETURN FALSE
file.data$ = "slydog | 12345"
file.Write()
file.Append("glbasic | 12346")

// Parsing a file line by line
IF NOT file.Open(FILE_MODE_READ, "graphics/fonts/comics.dat") THEN RETURN FALSE  // Exit and return 'FALSE' if can't open

REPEAT
    line$ = file.GetLine$()
    IF file.eof THEN GOTO SKIP // End of file  -> safe usage of 'GOTO' IMO :)
    IF LEN(line$) <= 0 THEN GOTO SKIP // Skip blank lines
    //  continue with parsing . . .
    SKIP:
UNTIL file.eof
file.Close()


The sample code wasn't tested, so no guarantees! 
And other commands weren't tested much either, so let me know if I have any bugs.
(or calls to my other functions I didn't include!)

I included my 'LOG()' command too as I find it useful for logging errors or for debugging code.
Also, it shows the time in ms since the program started so could be useful for identifying bottlenecks.
Here's a typical Output window for my game using this command:
Code (glbasic) Select
[   0.001] >GFX:{Sprite_Load     } fn:[Graphics/skin.png] | id:[1]
[   0.117] >GFX:{Sprite_Load     } fn:[Graphics/preview.png] | id:[3]
[   0.120] >GFX:{Sprite_Load     } fn:[Graphics/Fonts/blambot_secret_origins_32x50.png] | id:[4]
[   0.182] >GFX:{Sprite_Load     } *** File NOT FOUND!: [Graphics/Fonts/Kartika.png]
[   0.183] >FIL:{TFile.Open      } *** Error Opening File: [Graphics/Fonts/Kartika.dat] ***
[   0.184] >FON:{TGlyph.New%     } *** Error Opening Glyph File: [Graphics/Fonts/Kartika.dat] ***
[   0.186] >GFX:{Sprite_Load     } fn:[Graphics/Skins/Skin_Basic.png] | id:[5]
[   0.207] >GFX:{TSpriteUv       } sprite:[ 5] | rgb:[240,200,  0] | uv:[187,178]-[227,218]
[   0.234] >GFX:{TSpriteUv       } sprite:[ 5] | rgb:[  0,  0,255] | uv:[  0,192]-[ 48,240]
[   0.235] >GFX:{TSpriteUv       } sprite:[ 5] | rgb:[  0,  0,255] | uv:[ 48,192]-[ 96,240]
[   0.237] >GFX:{TSpriteUv       } sprite:[ 5] | rgb:[  0,  0,255] | uv:[ 96,192]-[144,240]
[   0.238] >GFX:{TSpriteUv       } sprite:[ 5] | rgb:[  0,  0,255] | uv:[144,192]-[186,240]
[   0.243] >GAM:{State           } STATE_MENU_MAIN
[   3.643] >GAM:{State           } STATE_MENU_WORLD
[   6.510] >GUI:{Scroll.IsScroll } FLICK Touch . . .
[   6.626] >GUI:{Scroll.IsScroll } FLICK Canceled: Finger Off
[   6.631] >GUI:{MenuWorld Select} id:[1000] | name:[Easy Peasy]
[   6.634] >GAM:{State           } STATE_MENU_LEVEL
[   6.728] >MAZ:{Maze_Generate   } >>>
[   6.742] >MAZ:{Maze_Generate   } <<< Done!
[   6.771] >GFX:{ShaderLoad      } fn:[/TwistedMaze/Window.app/Media/Graphics/Shaders/Maze]
[   6.887] >GFX:{ShaderLoad      } fn:[/TwistedMaze/Window.app/Media/Graphics/Shaders/Color]
[   8.770] >MAZ:{Maze_Generate   } >>>
[   8.777] >MAZ:{Maze_Generate   } <<< Done!
[   8.782] >GAM:{State           } STATE_PLAYING_MAZE
[   8.897] >GAM:{HudDraw         } stars:1
[  10.714] >GAM:{GLB_ON_QUIT     } Exiting >>>
#8
I have a common TYPE (A) that I initialize an instance with values at the program start.
I have another TYPE (B) that has a member of the first TYPE (A).

I want to use my previously initialized instance of (A) in multiple instances of (B).
This works fine automatically when I assign an instance of (A) to the member of an instance of (B).
It just copies the current values from (A).

But if I change values in the source instance of (A), those changes aren't reflected in the instance of (B).
Plus (B) needs as much extra memory as the size of TYPE (A).

What I would love is the ability to declare the member inside TYPE (B) as a pointer to TYPE (A).
That way when I change the instance (A), instances of (B) automatically update.
Plus I don't need the extra memory, just enough to hold the address of instance (A).

Is there a way to do this now?
Even if I have to go INLINE C?
#9
Whenever you run and quit your game, the IDE returns with the 'Jumps' tab selected.
I never use the 'Jumps' tab and this is very annoying.  (I live exclusively in the 'Files' tab since I have 25 source files)

It would be nice if the IDE remembered which tab you where in and returned to that same tab.
(or why is it even changing the tab to begin with?)

Actually I would prefer the 'Jumps' and 'Files' windows to be separate from each other.
I would use 'Jumps' more often if it were in front of me, but now I have to click on the tab first, then back to 'Files' when I'm done.
This may be difficult to do of course, but just throwing it out there in case other people agree, or it isn't that hard.

A good split for the windows could be to instead of having the 'Output' window fill the entire bottom, have it the same width as the editor window, and have the 'Jumps' / 'Files' window stretch to the bottom.  (Then you could display both 'Jumps' and 'Files' on top of each other.)

And, just curious about two other bugs that were previously recorded a while ago that are still occurring.

  • <CTRL>+<TAB> to switch code windows only works for a while but stops at some point (after running the game?)
    I'm suspecting this is because either the control receiving keyboard input isn't in focus or has changed somehow.

  • Incredibuild still isn't 100% working.  It still doesn't detect code changes to certain modules.  Interestingly it's always the same modules that it misses, while others it always picks up just fine. Now I'm experiencing a new situation where it says it is skipping a module (that has changed), but when I run the game it seems to have picked up the change.   

    I don't know how this is all handled and I'm surprised this is a problem, as you'd think the file date/time would always update and this would be a safe and accurate method to determine if a module has changed.

And all of these bugs seems to happen on both Windows XP 32bit and Windows 7 64bit.

Gernot, I know these may be difficult to pin down, since so few people are reporting these problems.  And, I don't know how to recreate them using a simple example, but I would be willing to send my entire project to you, as it could be a problem specific to my situation or setup.  (I can trust you right? ha  =D)

(ignore these smilies, just wanted to know if it looked like he was getting sick in the toilet:   :nw: :shit:     )
#10
Here's the <SRCFILES> section of my project file (.gbap), which was manually ordered:
Code (glbasic) Select
   <SRCFILES >
      <FILE PATH=".\MazeBall.gbas" />
      <FILE PATH=".\__LIB\_Common.gbas" />
      <FILE PATH=".\__LIB\_File.gbas" />
      <FILE PATH=".\__LIB\_Font.gbas" />
      <FILE PATH=".\__LIB\_gCamera.gbas" />
      <FILE PATH=".\__LIB\_gEntity.gbas" />
      <FILE PATH=".\__LIB\_gMesh.gbas" />
      <FILE PATH=".\__LIB\_gShader.gbas" />
      <FILE PATH=".\__LIB\_gSprite.gbas" />
      <FILE PATH=".\__LIB\_GUI.gbas" />
      <FILE PATH=".\__LIB\_gVector.gbas" />
      <FILE PATH=".\__LIB\_Input.gbas" />
      <FILE PATH=".\__LIB\_Sound.gbas" />
      <FILE PATH=".\__LIB\_Tween.gbas" />
      <FILE PATH=".\__Menus\Menu_Main.gbas" />
      <FILE PATH=".\__Menus\Menu_World.gbas" />
      <FILE PATH=".\__Menus\Menu_Level.gbas" />
      <FILE PATH=".\__Menus\Menu_Play.gbas" />
      <FILE PATH=".\__Play\Play_Maze.gbas" />
      <FILE PATH=".\__Play\Game_Over.gbas" />
      <FILE PATH=".\LevelDraw.gbas" />
      <FILE PATH=".\LevelMain.gbas" />
      <FILE PATH=".\Maze.gbas" />
      <FILE PATH=".\Gizmos.gbas" />
      <FILE PATH=".\_Main.gbas" />
   </SRCFILES>


But, whenever I recompile in the latest version of GLBasic [ver 9.033] it immediately sorts those entries by file name (ignoring the folder name) and resaves the .gbap project file's <SRCFILES> section as:
Code (glbasic) Select
   <SRCFILES >
      <FILE PATH=".\MazeBall.gbas" />
      <FILE PATH=".\__LIB\_Common.gbas" />
      <FILE PATH=".\__LIB\_File.gbas" />
      <FILE PATH=".\__LIB\_Font.gbas" />
      <FILE PATH=".\__LIB\_gCamera.gbas" />
      <FILE PATH=".\__LIB\_gEntity.gbas" />
      <FILE PATH=".\__LIB\_gMesh.gbas" />
      <FILE PATH=".\__LIB\_gShader.gbas" />
      <FILE PATH=".\__LIB\_gSprite.gbas" />
      <FILE PATH=".\__LIB\_GUI.gbas" />
      <FILE PATH=".\__LIB\_gVector.gbas" />
      <FILE PATH=".\__LIB\_Input.gbas" />
      <FILE PATH=".\_Main.gbas" />
      <FILE PATH=".\__LIB\_Sound.gbas" />
      <FILE PATH=".\__LIB\_Tween.gbas" />
      <FILE PATH=".\__Play\Game_Over.gbas" />
      <FILE PATH=".\Gizmos.gbas" />
      <FILE PATH=".\LevelDraw.gbas" />
      <FILE PATH=".\LevelMain.gbas" />
      <FILE PATH=".\Maze.gbas" />
      <FILE PATH=".\__Menus\Menu_Level.gbas" />
      <FILE PATH=".\__Menus\Menu_Main.gbas" />
      <FILE PATH=".\__Menus\Menu_Play.gbas" />
      <FILE PATH=".\__Menus\Menu_World.gbas" />
      <FILE PATH=".\__Play\Play_Maze.gbas" />
   </SRCFILES>


The problem is that I NEED the files to be compiled in a specific order or I get errors when declaring variables of a TYPE that was defined in another project file later in the compile order.  The solution for that error that worked for me was to ensure that the file defining the TYPE was always before any files that declared variables of that TYPE.
Oh, and this isn't a problem with my version 8 GLBasic.

I guess a work around would be to name files alphabetically by the order I want them compiled!  :blink:
#11
I haven't seen anybody else mention this yet, and it's been happening for quite a while now.
A lot of times when I hit [CTRL] + Z (undo), instead of undoing the latest change, it actually undoes the latest two changes.
This is on multiple lines, which I know it shouldn't do.
It's like the undo buffer is brought back two steps instead of one.

It's not every time, but it happens about once a day.
This can be potentially dangerous because sometimes it may not be obvious and you may not notice the second undo.
Just wondering if anybody else experienced this?
#12
Is there a way to display the current vertice (or poly) count from OpenGL (or from GLBasic if it has that info)?

I'm adjusting the FAR CLIP limit of the camera in hopes of saving some polys on an iPhone.
But I don't know how to figure out the benefits if I don't know how many polys I'm saving.

Or it would also be handy for dynamic worlds.
You could limit how much detail to draw up until you reach your vertice limit for that device.

Simply counting the number of vertices/poly's in all my models doesn't help since I don't know what models will be clipped from view.
#13
I know this wont be easy to do, but a nice feature would be the ability to 'auto hide' those tabs, similar to how Visual Studio does it for the Tool Box, Solution Explorer, Properties, etc.

Just a small vertical tab would be visible until you hover over it, then it would auto-expand displaying its contents.
It would be extra nice to be able to dock each tab separately to either side of the IDE.
And, each tab could either be Pinned to a side, or have an Auto-Hide option.

This would increase the real estate on the screen available for viewing / editing the code.

Or, it would at least be nice to have the 'Jumps' and 'Files' tabs to be both open at once without having to click each tab to change.  I use those two tabs frequently and its inconvenient to keep switching.
Either have them above/below each other with a splitter bar separating them, or have them beside each other (less real estate!), or one on each side of the IDE (with an option to choose how you want them displayed).

While we're at it, the 'Output' and 'Search' window at the bottom could also Auto-Hide!

And, does anybody else find the opening splash screen to be intrusive?  Its always an extra click to open your project because its in the way.  Just my opinion.

Ha, simple!  Just thought I'd throw this out there.
#14
I'm very new to 3D programming, and therefore 3D math.

I'm creating a path system where you create a bunch of path points and have an object (model, camera) follow that path.
Its working fairly well and follows the path, interpolating the position between the current two points based on those points time duration.  (It's only a linear interpolating for now, no smoothing).

I'm trying to implement an 'Orient To Path' option that will rotate the object to face the direction of the current path.
(Similar to the camera's 'LookAt' parameters).
So the numbers I have are the path start point and end point, and they are stored in a 3D vector TYPE (x,y,z).
I can take the difference of the two (end point - start point) to end up with one vector (named 'v') starting at (0,0,0).

Now, I want to rotate my object to match that vector's angle/direction.
I have the 'Y' rotation working with this:
   v_rotate.y = ATAN(v.z, v.x)
But even that acts weird and I had to add 180 or something to get it working as expected.
I'm not even sure of the range of numbers ATAN returns?  (-180 to 180?)  So then just add 180?
Or I've seen this?:  v_rotate.y = MOD(ATAN(v.z, v.x), 360)

But, I can't even come close to getting the 'X' rotation working.  (And I figured out that the 'Z' rotation is needed too?)
I had thought that if I rotate on the 'Y' axis first so I'm heading in the desired direction, then I could just figure out my X angle (relative to the direction I'm facing, ie. how much to tilt up or down).  It would be the same formula somewhat.  But I think I've come to learn that the 'X' rotation is always 'World' aligned?  And I can't make it relative to the new 'Y' rotation?  I hope that makes sense.

I think I have to somehow figure out this 'Angle' for the 'X' rotation, and split that value between 'X' and 'Z' somehow?

I've tried this many times and have failed.  (I find so many 2D examples, but that third dimension is eluding me!)
Any ideas?

Thanks,
-Doug
#15
I have a library that I call from my main project module.
In this library, I set up some global constants.
Then, I tried to make another constant for a default value that assigns the value of one of the above constants, like this: (In Tween.gbas)
Code (glbasic) Select
CONSTANT kTweenTransition_Linear = 1
CONSTANT kTweenTransition_Spring = 2
CONSTANT kTweenTransition_Bounce = 3

CONSTANT kTweenTransition_Default = kTweenTransition_Spring


But, in my main module, when I refer to the constant that was assigned as default (kTweenTransition_Spring in this case), I get this C++ error:
error: `kTweenTransition_Spring' was not declared in this scope.

But when I assign a hard coded value like:
Code (glbasic) Select
CONSTANT kTweenTransition_Default = 2
everything works fine.

It seems assigning a constant to a constant causes the original constant to be declared earlier in the compiling cycle?

[edit]
Or never gets created in the first place? 
Or because it would have been created as a standard float in the first pass?
#16
1. When I <ALT>+<TAB> back into the GLBasic editor, it removes my editing cursor so I have to keep re-clicking the code.
    - This works fine initially, but I have noticed quite frequently that once this behavior starts, it persists.
    - I haven't noticed what brings it back to normal, I assume by exiting and restarting.

2. <CTRL>+<TAB>, like above, doesn't always work for switching documents, and nothing happens.
    - It usually works fine for a while when GLBasic is freshly started.

3. <CTRL>+<TAB> again.  Not so much a bug, but a request.
    - When holding <CTRL> and repeatedly pressing <TAB>, it cycles through all the windows fine, as expected.
    - But if I hit <CTRL>+<TAB> once, it switches to the next window.  If I repeat the key sequence, it SHOULD return to the previously edited window, but it instead displays the NEXT window in the queue, like if I use the key sequence above.
    - ie. <CTRL>+(<TAB>, <TAB>) should be different than <CTRL>+<TAB>, <CTRL>+<TAB>
    - This feature would be handy when going back and forth between two documents, now I have to cycle through all the others first before I return to the one I was just editing.  (ha, I *could* close all the other windows first, but it'd be a pain)
    - I hope I explained that clearly.

4. When changing the display colors in the Options menu, it doesn't always get reflected back in the editor.  Reload fixes it.

5. Request: Add more color options:  Comment background, Margin / Editor separator line,  Function Hilighter (uses 'Normal Text' now), how about the Function Hilighter width?

6. Request: 'Jumps' tab: Space/line/(or different color) below 'Show All', 'Sort: 1.2.3' to show Functions better.

This happens when I'm using the demo version (didn't purchase full version yet) in both Windows XP Pro 32bit, and Windows 7 64bit.

Thanks, and great product, amazing price.
#17
I think this is a bug, and not a feature.
The 'Z' coordinate (so, we're viewing our files from the top view? ha) seems to work.

The 'X' coordinate seems to count the bytes of text, not the actual horizontal position.
This would work fine until you insert a 'Tab' character.
It displays four 'spaces', but takes only one byte.
With 3 tabs inserted to your left, it will show you as on position 4, not 13 like is should, imo.

I guess to fix it just add (tabsQtyInLine * 3) to the current position?  (Assuming the tab size if fixed at 4).