Hi everyone,
I'd like to know how to implement a camera for 2D games (Top-down view) in GLBasic so that it isn't necessary to move every object in the world instead of the player.
Most engines nowaday support this out of the box so I might have missed a built-in function.
I'm glad about any help! :)
Have a look for info on the VIEWPORT command.
You can also use the POLYVECTOR commands to do the same thing. With both of those you can have independent 2D split screen mutiplayer games working from just one background screen.
Thank you.
I just don't really get how Viewport is supposed to be used (at least it doesn't do what I want).
Could you give me an example of how to use it?
If your goal is a 2D I recommend to forget all about cameras. Consider your screen as is and put there your 2D stuff like sprites or tiles and move it arround the screen. If you need scroll move all tiles and sprites at once. You move all your world, not the camera. The camera is always fixed. And use types to gather information about all your sprites an objects in your game. Use polyvectors to better performace (recomended for lots of tiles an sprites).
Some tools do it through '2D emulation', as all scenes are 3D there and just camera is moved on X Y axis for normal movement, and on Z for zooming, yet it's some kind of slow from demos that I have checked some time ago so I think that they do some calculations in background.
As GLB is normal programming language not engine, you'r not bounded by some limitations imposed by engine itself..
Depending on your game, special effects that you want to use and so on, you have 3 options:
1. Viewport
2. OpenGL matrix transformations
3. Render to Texture / Offscreen
Currently I'm using option 2, and I'm prototyping with this little camera code:
TYPE Point_2d
x%
y%
ENDTYPE
//2d camera type
TYPE game_camera_2d
tile_size% //domyslna wielkosc tile mapy
tile_size_shifted%
przes_mapy_px AS Point_2d //MIN przesuniecie TO 0,0
przes_mapy_tile AS Point_2d
przes_mapy_czesciowe AS Point_2d
visible_rect_px AS pg_rect //faktycznie widoczny prostokat
visible_rect_tiles AS pg_rect //przeliczonyna tilesy
zoom_current# //zoom kamery
zoom_min#
zoom_max#
zoom_default#
move_boundries AS Point_2d
map_size_px AS Point_2d //wielkosc mapy w px
map_size_tiles AS Point_2d //w tilesach
camera_base_size AS Point_2d //wielkosc canvasa na ktorym sie rysuje
camera_curr_size AS Point_2d //wielkosc aktualnego pola widzenia kamery
camera_curr_tiled_size AS Point_2d //wielkosc widzianego pola w tilesach
//ustawia podstawowe dane kamery
FUNCTION initCamera%: sizex%, sizey%, tile_size_xy%, map_width_px%, map_height_px%, boundries%
LOCAL i1%, i2%
self.tile_size% = tile_size_xy%
IF (boundries% = 1) //ustaw ograniczenie przesuwania
self.move_boundries = pg_return_p2d(tile_size_xy%, tile_size_xy%)
ELSE
self.move_boundries = pg_return_p2d(0, 0)
ENDIF
self.przes_mapy_px = pg_return_p2d(self.move_boundries.x%, self.move_boundries.y%)
self.camera_base_size = pg_return_p2d(sizex%, sizey%)
//1,2,4,8,16,32,64,128,256 - sprawdzic czy poprawna wielkosc tile, wyliczyc shifted
FOR i1% = 1 TO 8
i2% = POW(2, i1%)
IF (i2% >= self.tile_size%)
self.tile_size% = i2%
self.tile_size_shifted% = i1%
BREAK
ENDIF
NEXT
IF (map_width_px% >= 0)
setMapSize_px(map_width_px%, map_height_px%)
ELSE
setMapSize_tiled(ABS(map_width_px%), ABS(map_height_px%))
ENDIF
setZoomMinMax(1.0, 5.0)
calcMinZoom(0)
setCameraZoom(1.0, 1)
calcVisibleRect()
//calc visibility
ENDFUNCTION
//ustawia wielkosc mapy w pikselach
FUNCTION setMapSize_px%: width%, height%
self.map_size_px = pg_return_p2d(width%, height%)
self.map_size_tiles = pg_return_p2d(width% / self.tile_size%, height% / self.tile_size%)
ENDFUNCTION
//ustawia wielkosc mapy w tilesach
FUNCTION setMapSize_tiled%: tilesx%, tilesy%
self.map_size_px = pg_return_p2d(tilesx% * self.tile_size%, tilesy% * self.tile_size% )
self.map_size_tiles = pg_return_p2d(tilesx%, tilesy%)
ENDFUNCTION
FUNCTION centerCameraOnP2D%: newx%, newy%
//self.
ENDFUNCTION
//podstawowe poruszanie kamera, uwzgledania przeliczenia skali
FUNCTION moveCameraByP2D%: movex%, movey%, scaled%
IF (movex% = 0 AND movey% = 0) THEN RETURN 0
IF (scaled% = 1)
movex% = INTEGER(movex% / self.zoom_current#); movey% = INTEGER(movey% / self.zoom_current#)
ENDIF
LOCAL newx%, newy%
newx% = self.przes_mapy_px.x% + movex%; newy% = self.przes_mapy_px.y% + movey%
IF (newx% <> self.przes_mapy_px.x% OR newy% <> self.przes_mapy_px.y%)
IF (newx% > (self.map_size_px.x% - self.camera_curr_size.x% - self.move_boundries.x%)); newx% = (self.map_size_px.x% - self.camera_curr_size.x% - self.move_boundries.x%); ENDIF
IF (newx% < self.move_boundries.x%); newx% = self.move_boundries.x%; ENDIF
IF (newy% > (self.map_size_px.y% - self.camera_curr_size.y% - self.move_boundries.y%)); newy% = (self.map_size_px.y% - self.camera_curr_size.y% - self.move_boundries.y%); ENDIF
IF (newy% < self.move_boundries.y%); newy% = self.move_boundries.y%; ENDIF
//jezeli pozycja sie zmienila to przeliczyc widoczny teren
self.przes_mapy_px = pg_return_p2d(newx%, newy%)
self.przes_mapy_tile = pg_return_p2d(ASR(newx%, self.tile_size_shifted%), ASR(newy%, self.tile_size_shifted%))
self.przes_mapy_czesciowe = pg_return_p2d(MOD(newx%, self.tile_size%), MOD(newy%, self.tile_size%))
calcVisibleRect()
ELSE
//nie bylo zmiany pozycji kamery, nie ma co przeliczac
ENDIF
RETURN 1
ENDFUNCTION
FUNCTION setCameraPos%: left%, top%
//self.przes_mapy_px = pg_return_p2d(left%, top%)
//IF (self.przes_mapy_px.x% < 0) THEN self.przes_mapy_px.x% = 0
//IF (self.przes_mapy_px.y% < 0) THEN self.przes_mapy_px.y% = 0
self.przes_mapy_px = pg_return_p2d(0, 0)
self.moveCameraByP2D(left%, top%, 0)
calcVisibleRect()
ENDFUNCTION
FUNCTION calcVisibleRect%:
//rect_px.left = przes_x
//right = left% + curr_size_x
//
self.visible_rect_px = pg_return_pgrect_pos(self.przes_mapy_px.x%, self.przes_mapy_px.y%, self.przes_mapy_px.x% + self.camera_curr_size.x% - 1, self.przes_mapy_px.y% + self.camera_curr_size.y% - 1)
self.visible_rect_tiles = pg_return_pgrect_pos(ASR(self.visible_rect_px.left%, self.tile_size_shifted%), ASR(self.visible_rect_px.top%, self.tile_size_shifted%), ASR(self.visible_rect_px.right%, self.tile_size_shifted%), ASR(self.visible_rect_px.bottom%, self.tile_size_shifted%))
self.camera_curr_tiled_size = pg_return_p2d(self.visible_rect_tiles.right% - self.visible_rect_tiles.left% + 1, self.visible_rect_tiles.bottom% - self.visible_rect_tiles.top% + 1)
//myDEBUG("widzany rect tile: " + self.visible_rect_tiles.left% + "," + self.visible_rect_tiles.top% + "," + self.visible_rect_tiles.right% + "," + self.visible_rect_tiles.bottom%)
ENDFUNCTION
FUNCTION setZoomMinMax%: scale_min#, scale_max#
self.zoom_min# = scale_min#; self.zoom_max# = scale_max#
ENDFUNCTION
FUNCTION zoomInOut%: value#, center_camera%
//-(new_size_x - old_sz) / 2
LOCAL old_zoom# = self.zoom_current#, old_size AS Point_2d = self.camera_curr_size
//LOCAL new_zoom# = self.zoom_current# + value#
setCameraZoom(self.zoom_current# + value#, 1)
IF (old_zoom# <> self.zoom_current#)
//LOCAL old_size AS Point_2d = self.camera_curr_size
//self.zoom_current# = new_zoom#
//jezeli jest zmiana to przeliczyc pozycje / przesuniecie
IF (center_camera% = 1)
//ge_przesun_mape(-(self.camera_curr_size.x% - old_size.x%) / 2, -(self.camera_curr_size.y% - old_size.y%)/2)
//moveCameraByP2D(-(self.camera_curr_size.x% - old_size.x%) / 2, -(self.camera_curr_size.y% - old_size.y%)/2, 0)
ENDIF
ENDIF
ENDFUNCTION
//ustawia przyblizenie kamery
FUNCTION setCameraZoom%: value#, absolute%
myDEBUG("---" + value# + ", absolute: " + absolute%)
LOCAL scale_step# = 0.5 //scale_step# - dokladnosc przyblizenia skali
IF (absolute% = 1)
self.zoom_current# = value#
ELSE //ustalanie nowej skali z przyblizenia
self.zoom_current# = 1.0
WHILE (self.zoom_current# <= value#)
INC self.zoom_current#, scale_step#
WEND
ENDIF
IF (self.zoom_current# < self.zoom_min#); self.zoom_current# = self.zoom_min#; ENDIF
IF (self.zoom_current# > self.zoom_max#); self.zoom_current# = self.zoom_max#; ENDIF
IF (self.zoom_current# = 1.0) //brak skalowania
ge_skala_gry_spriteid% = -1
self.camera_curr_size = pg_return_p2d(self.camera_base_size.x%, self.camera_base_size.y%)
ELSE //mamy skalowanie
ge_skala_gry_spriteid% = 5
self.camera_curr_size.x% = INTEGER(self.camera_base_size.x% / self.zoom_current#)
self.camera_curr_size.y% = INTEGER(self.camera_base_size.y% / self.zoom_current#)
//CREATESCREEN 5, ge_skala_gry_spriteid%, self.camera_curr_size.x%, self.camera_curr_size.y%
ENDIF
//IF (1 = 1) THEN self
self.setCameraPos(self.przes_mapy_px.x%, self.przes_mapy_px.y%)
//calcVisibleRect()
//myDEBUG(GETTIMERALL() + "kreacja libacja")
myDEBUG("--ustawiamy skale: " + self.zoom_current# + ", MIN/MAX skala: " + self.zoom_min# + "/" + self.zoom_max# + ", csize: " + self.camera_curr_size.x% + "x" + self.camera_curr_size.y%)
//RETURN self.zoom_current#
ENDFUNCTION
FUNCTION calcMinZoom%: value%
LOCAL min_skala#
min_skala# = (pg_curr_resx% * 1.0) / self.map_size_px.x%
myDEBUG(">>>" + pg_curr_resx% + ", map_width" + self.map_size_px.x%)
IF (min_skala# < 1.0) THEN min_skala# = 1.0
self.zoom_min# = 1.0
WHILE (self.zoom_min# < min_skala#)
INC self.zoom_min#, 0.5
WEND
myDEBUG("---MIN SKALA: " + self.zoom_min# + ", MIN val: " + min_skala#)
ENDFUNCTION
ENDTYPE
Beware of uncommented code, and all comments are in Polish, yet you can see how things are done..
To use this in you project:
//define camera object
Global my_camera as game_camera_2d
//init camera - after preparing level/map
//canvas - size of camera draw destination, for single view games use screen size
//tile_size - size of your map tiles, mostly 16 or 32
//map size: >0 size in pixels, <0 size in tiles
//bounds - 0 or 1, use first & last maptiles as unpassable/drawable boundries
my_camera .initCamera(canvas_width, canvas_height, tile_size, map_width, map_height, bounds)
//below call with init camera on screen 800x600, with tiles size = 32, and map thats 128x32 in tiles.
//my_camera.initCamera(800, 600, 32, -128, -32, 1)
//zomm out
my_camera .zoomInOut(-1.0, 1)
//zoom in
my_camera .zoomInOut(1.0, 1)
//to move camera
my_camera .moveCameraByP2D(cx%, cy%, 0)
//set new map size
my_camera .setMapSize_tiled(size_x%, size_y%)
//for transformations with viewport / opengl use: my_camera.przes_mapy_px
//for calculationg what objects are visible - if you need that, use: my_camera.visible_rect.px
And as obvious, for larger projects, where you have hundreds of objects placed on large map, camera like this is proper way to deal with them (as you don't do all those calculations for draw position). For small games, even with scrolled maps but less objects it may not be needed.