2D camera

Previous topic - Next topic


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! :)

Ian Price

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.
I came. I saw. I played.


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:
Code (glbasic) Select

TYPE Point_2d
//2d camera type
TYPE game_camera_2d
    tile_size%          //domyslna wielkosc tile mapy
    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

    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%)
            self.move_boundries =  pg_return_p2d(0, 0)
        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%


        IF (map_width_px% >= 0)
            setMapSize_px(map_width_px%, map_height_px%)
            setMapSize_tiled(ABS(map_width_px%), ABS(map_height_px%))
        setZoomMinMax(1.0, 5.0)
        setCameraZoom(1.0, 1)

        //calc visibility


    //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%)

    //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%)

    FUNCTION centerCameraOnP2D%: newx%, newy%

    //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#)
        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%))
            //nie bylo zmiany pozycji kamery, nie ma co przeliczac
        RETURN 1

    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)

    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%)


    FUNCTION setZoomMinMax%: scale_min#, scale_max#
        self.zoom_min# = scale_min#; self.zoom_max# = scale_max#

    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)



    //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#

        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%
        //IF (1 = 1) THEN self

        self.setCameraPos(self.przes_mapy_px.x%, self.przes_mapy_px.y%)
        //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#

    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

        myDEBUG("---MIN SKALA: " + self.zoom_min# + ", MIN val: " + min_skala#)



Beware of  uncommented code, and all comments are in Polish, yet you can see how things are done..
To use this in you project:
Code (glbasic) Select
//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.
Check my source code editor for GLBasic - link Update: 20.04.2020