3rd Person Camera with documentation

Previous topic - Next topic

Synthetic

This is a fairly simple 3rd person camera system found in many games. I've added documentation so you can see what's going on and I've also used easy to read variable names too as you navigate through the workings. This mainly focuses on creating a 3rd person camera but also includes things like timer based movement. I do plan on adding more features later and I'm always open to suggestions and optimizations.

Here is the media if you don't want to bother with the rar:




Code (glbasic) Select

// =================================================================================================
// GLBasic 3rd Person Camera
// By: Synthetic
//
// - This is a fairly simple 3rd person camera system found in many games. I've added documentation
//   so you can see what's going on and I've also used easy to read variable names too as you
//   navigate through the workings. This mainly focuses on creating a 3rd person camera but also
//   includes things like timer based movement.
//
// - I do plan on adding more features later and I'm always open to suggestions and optimizations.
//
// ===========================
// CONTROLS
// ===========================
// Mouse Look
// - Hold the right mouse button to freely move around the character entity 360 degrees on the x/z
//   axis. Y axis view curves from overhead to up close around ground level.
//
// Movement Keys
// - Movement is done by rotating character entity via A/D and using W/S for forward/reverse.
//
// ===========================
// NOTES
// ===========================
// - The land generation code was taken from Gernot's Islands demo and modified slightly.
//
// =================================================================================================
// Load media
// =================================================================================================

LOADSPRITE "land.png",1
LOADSPRITE "cursor1.png",2
LOADSPRITE "cursor2.png",3

// =================================================================================================
// Declare global variables and values
// =================================================================================================

nil = 0 // Declared to keep compiler from throwing a warning
mouse_x = 0 // Declared to keep compiler from throwing a warning
mouse_y = 0 // Declared to keep compiler from throwing a warning
char_pos_y = 0 // Declared to keep compiler from throwing a warning

start_time = GETTIMERALL() // Grab intital time for calculating FPS
current_time = GETTIMERALL() // Grab intital time for calculating timer based movement

cam_mouse_x = 90 // Set camera starting view angle
cam_mouse_y = 32 // Set camera starting position on y axis
camera_near = 100 // Min distance from camera that will be drawn on screen
camera_far = 6000 // Max distance from camera that will be drawn on screen
camera_distance = 2500 // Adjusts how big of an orbit camera will rotate from character
distance_fog = 1 // Enable or disable distance fog 0 = off | 1 = on
fog_color = RGB(200,200,200) // Sets the color of the distance fog
fog_near = 1000 // Sets distance from camera that the fog starts
fog_far = 3000 // Sets ending distance of fog
light_color = RGB(255,255,255) // Sets ambient world lighting color
movement_speed = 150 // Sets how fast character moves
rotation_speed = 250 // Sets how fast character turns/changes direction

c1 = RGB(255,255,255) // One of the colors for our character object
c2 = RGB(00,150,255) // One of the colors for our character object
c3 = RGB(200,0,0) // One of the colors for our character object
c4 = RGB(0x50, 0x70, 0xff) // Color for the fake skybox
sky_box_size = 2000 // Sets the size of the fake skybox

// =================================================================================================
// Create Objects, Land and others
// =================================================================================================

// Generic entity (player) mmm...triangles...cheese filled torilla triangles!
X_OBJSTART 2
X_OBJADDVERTEX   0,   0,   0,   0,0,c1
X_OBJADDVERTEX   0,  -1,   0,   0,0,c2
X_OBJADDVERTEX -10,   0,   2,   0,0,c1
X_OBJADDVERTEX -10,  -1,   2,   0,0,c2
X_OBJADDVERTEX -10,   0,  -2,   0,0,c1
X_OBJADDVERTEX -10,  -1,  -2,   0,0,c2
X_OBJADDVERTEX   0,   0,   0,   0,0,c2
X_OBJADDVERTEX -10,  -1,  -2,   0,0,c2
X_OBJADDVERTEX -10,  -1,   2,   0,0,c2
X_OBJADDVERTEX   0,  -1,   0,   0,0,c2
X_OBJEND

X_OBJSTART 3
X_OBJADDVERTEX   0,  .1,   0,   0,0,c3
X_OBJADDVERTEX -10,  .1,  .5,   0,0,c3
X_OBJADDVERTEX -10, -.1, -.5,   0,0,c3
X_OBJEND

// Sky Box
X_OBJSTART 4
X_OBJADDVERTEX  sky_box_size, -sky_box_size,  sky_box_size, 1,0,c4
X_OBJADDVERTEX -sky_box_size, -sky_box_size,  sky_box_size, 0,0,c4
X_OBJADDVERTEX  sky_box_size,  sky_box_size,  sky_box_size, 1,1,c4
X_OBJADDVERTEX -sky_box_size,  sky_box_size,  sky_box_size, 0,1,c4
X_OBJNEWGROUP
X_OBJADDVERTEX -sky_box_size,  sky_box_size, -sky_box_size, 1,1,c4
X_OBJADDVERTEX -sky_box_size, -sky_box_size, -sky_box_size, 1,0,c4
X_OBJADDVERTEX  sky_box_size,  sky_box_size, -sky_box_size, 0,1,c4
X_OBJADDVERTEX  sky_box_size, -sky_box_size, -sky_box_size, 0,0,c4
X_OBJNEWGROUP
X_OBJADDVERTEX -sky_box_size,  sky_box_size,  sky_box_size, 0,0,c4
X_OBJADDVERTEX -sky_box_size,  sky_box_size, -sky_box_size, 0,1,c4
X_OBJADDVERTEX  sky_box_size,  sky_box_size,  sky_box_size, 1,0,c4
X_OBJADDVERTEX  sky_box_size,  sky_box_size, -sky_box_size, 1,1,c4
X_OBJNEWGROUP
X_OBJADDVERTEX  sky_box_size, -sky_box_size, -sky_box_size, 0,1,c4
X_OBJADDVERTEX -sky_box_size, -sky_box_size, -sky_box_size, 1,1,c4
X_OBJADDVERTEX  sky_box_size, -sky_box_size,  sky_box_size, 0,0,c4
X_OBJADDVERTEX -sky_box_size, -sky_box_size,  sky_box_size, 1,0,c4
X_OBJNEWGROUP
X_OBJADDVERTEX  sky_box_size,  sky_box_size, -sky_box_size, 1,1,c4
X_OBJADDVERTEX  sky_box_size, -sky_box_size, -sky_box_size, 1,0,c4
X_OBJADDVERTEX  sky_box_size,  sky_box_size,  sky_box_size, 0,1,c4
X_OBJADDVERTEX  sky_box_size, -sky_box_size,  sky_box_size, 0,0,c4
X_OBJNEWGROUP
X_OBJADDVERTEX -sky_box_size, -sky_box_size,  sky_box_size, 1,0,c4
X_OBJADDVERTEX -sky_box_size, -sky_box_size, -sky_box_size, 0,0,c4
X_OBJADDVERTEX -sky_box_size,  sky_box_size,  sky_box_size, 1,1,c4
X_OBJADDVERTEX -sky_box_size,  sky_box_size, -sky_box_size, 0,1,c4
X_OBJNEWGROUP
X_OBJEND

// =================================================================================================
// Create land map
// =================================================================================================

MAXX=25
MAXY=25
DIM Height[MAXX+1][MAXY+1]

FOR x=1 TO MAXX-1
FOR y=1 TO MAXY-1
Height[x][y]=(SIN(45*8+x*360/MAXX)+COS(72*4+y*360/MAXY))*100
NEXT
NEXT

FOR n=0 TO 220
xs = RND(MAXX)
xe = MAXX-RND(MAXX)-1
ys = RND(MAXY)
ye = MAXY-RND(MAXY)-1
NEXT

FOR n=0 TO 7
xs = RND(MAXX)
xe = xs+RND(MAXX/2)+1; IF xe>MAXX-1 THEN xe=MAXX-1
ys = RND(MAXY)
xe = ys+RND(MAXY/2)+1; IF ye>MAXY-1 THEN ye=MAXY-1
NEXT

DrawMap(1, 2)

// =================================================================================================
// Main
// =================================================================================================

WHILE TRUE

// =============================================================
// Setup camera position based on mouse
// =============================================================

// OK, so since this is going to be 3rd person with mouse look that orbits around the character,
// we need to keep track of mouse movement. You can do this by setting up a couple variables as
// counters. What they will be counting is the velocity (speed & direction) of the mouse x/y axis.
// To get this we use the mouseaxis() command. With that, our end result will be a camera that
// moves with how fast we move our mouse but there is more before we can get useable coordinates.
// Now the next step is to keep things within range. While it's not necessary here, it keeps things
// neater so to speak and can be used for limiting range which is what I did with the y axis.
// I took it a bit different though for the y axis so as to curve from up close to an overhead view.
// of the player. It's not completely perfect for what I wanted but will work here to give an idea
// of the process. Generally in a world based game with ground, you wouldn't want to spin all the
// way around on the y axis and have the camera travel under the floor. On a more advanced note,
// you can even do collision checking in regards to the height of the map at the location of the
// camera to keep it from clipping into say a steep mountain side as you walk down it. Back to the
// explaination here. If you have never messed with this stuff, you might wonder how were going to
// take our mouse counters and convert that to 3d space coordinates. This is actually very easy
// thanks to two simple friends from Trigonometry. Sin and Cos. I should punch myself for that.
// Anyway, I won't explain them but what you need to know is that they will give us usable
// coordinates for our camera. One thing we have to do though is consider our third dimension
// which is depth. While 2D is x and y, 3D also has z. In the GLBasic world and many others, x is
// left to right, y is up and down, and z is in and out so to speak. If you have trouble visualising
// this, refer to Graphics (3D) in the GLBasic help. So our y axis becomes z in 3D space. On to the
// actual math, camera position x is derived by using Sin on our mouse x counter and camera position
// z is derived by using Cos on our mouse x counter. One more step is needed though and that's the
// distance of the camera from the character. By looking below you should have an idea of the formula:
//
// 3d_position_x = distance * sin(mouse_x)
// 3d_position_y = distance * cos(mouse_x)
//
// The distance is how far we will be doing our rotation or orbit at. A higher amount would be
// farther away from the character and a lower would be closer. On to the last part here, You will
// notice that I have factored in the mouse y counter to add some curving like I mentioned before
// instead of just moving up and down on the y axis. I would suggest reading up on some Trig if you
// don't know it and want to get a better grasp of the types of curves you might want. Google is your
// friend. :D

// Keep track of mouse coordinates for our camera on right click
IF MOUSEAXIS(4)
cam_mouse_x = cam_mouse_x - MOUSEAXIS(0)
cam_mouse_y = cam_mouse_y + MOUSEAXIS(1)
IF cam_mouse_x < 0 THEN cam_mouse_x = cam_mouse_x + 360
IF cam_mouse_x > 360 THEN cam_mouse_x = cam_mouse_x - 360
IF cam_mouse_y < 32 THEN cam_mouse_y = 32
IF cam_mouse_y > 719 THEN cam_mouse_y = 719
ENDIF

// Calculate camera position in regards to mouse coordinates
cam_pos_x = camera_distance * SIN(cam_mouse_x) * SIN(cam_mouse_y / 4 ) / 3
cam_pos_z = camera_distance * COS(cam_mouse_x) * SIN(cam_mouse_y / 4 ) / 3

// =============================================================
// Setup movement with the WASD keys
// =============================================================

// For movement, I have created a system that uses a directional vector. Depending on your direction,
// you will move forward in that direction and the camera will follow. First thing to setup is the
// rotation which is easy as checking the A and D keys and increasing or decreasing a counter depending
// on which key is pressed. Very simple. As before with the mouse looking, keep the direction
// within 360 degrees. Then just get the numbers via Cos and Sin and multiply those by how fast we want
// to move. The formula for the movement is:
//
// speed * position
//
// You then create variables that will hold the increased or decreased amount which will be added to
// the cameras final position. There is also something at work here which is important. The variable
// frame_delay_amount. This is the number used to calculate timer based movement. It is used to
// compensate movement speed on slower computers. For example, say your computer is slower than mine.
// On my computer the character turns a perfect 360 degrees every second. On yours, it is every 4
// seconds. What timer based movement does is allow us to take the amount you should have turned in
// x amount of time and add in the missing movement so you are also turing 360 degrees every second.
// How to get the delay amount will be found later on but the basic formula for your time compensated
// movement is:
//
// speed * delay between frames
//
// This also fixes the problem of moving slower in a scene with lots of 3D objects or too fast in a
// scene with vary few 3D objects to draw. Consistancy is a good thing.

// A - Rotate character counter clockwise
IF KEY(30) = 1
char_direction = char_direction - rotation_speed * frame_delay_amount
ENDIF
// D - Rotate character clockwise
IF KEY(32) = 1
char_direction = char_direction + rotation_speed * frame_delay_amount
ENDIF

// Keep character rotation within 360 degrees
IF char_direction < 0 THEN char_direction = char_direction + 360
IF char_direction > 360 THEN char_direction = char_direction - 360

// Get vector from character direction
char_rot_vec_x = COS(char_direction)
char_rot_vec_z = SIN(char_direction)

// W - Move character vector forward in regards to facing direction and speed
IF KEY(17) = 1
char_pos_x = char_pos_x + (movement_speed * char_rot_vec_x) * frame_delay_amount
char_pos_z = char_pos_z + (movement_speed * char_rot_vec_z) * frame_delay_amount
ENDIF
// S - Move character vector backwards in regards to facing direction and speed
IF KEY(31) = 1
char_pos_x = char_pos_x - (movement_speed * char_rot_vec_x) * frame_delay_amount
char_pos_z = char_pos_z - (movement_speed * char_rot_vec_z) * frame_delay_amount
ENDIF

// =============================================================
// Setup 3D World
// =============================================================

// So in the movement section we calculated our movement and ended up with the numbers needed to
// move our character around. Now it's time to get the camera to follow that. All we have to do
// is subtract the camera's current x and z coorinates from the character's x and z coordinates
// for the final camera position and do the same for the camera's view but instead of subtract,
// we add them. The rest is fairly straight forward but there is one more section about getting
// the delay time for timer based movement below.

// Calculate final camera position and view vectors in regards to character position
cam_final_pos_x = char_pos_x - cam_pos_x
cam_final_pos_y = cam_mouse_y
cam_final_pos_z = char_pos_z - cam_pos_z
cam_final_view_x = char_pos_x + cam_pos_x
cam_final_view_y = 0
cam_final_view_z = char_pos_z + cam_pos_z

X_MAKE3D camera_near, camera_far, 45
X_CAMERA cam_final_pos_x, cam_final_pos_y, cam_final_pos_z, cam_final_view_x, cam_final_view_y, cam_final_view_z

// Make our cheap skybox - Just for looks, not functional
X_MOVEMENT 0,0,0
X_DRAWOBJ 4, 0
X_CLEAR_Z

// Draw Character Entity and position
X_CULLMODE 0 // Set to 0 because we can view some of the backsides
X_MOVEMENT char_pos_x,0,char_pos_z
X_ROTATION char_direction,0,-10,0
X_DRAWOBJ 2,0
X_MOVEMENT char_pos_x,0,char_pos_z
X_ROTATION char_direction,0,-10,0
X_DRAWOBJ 3,0

// Create distance fog
IF distance_fog THEN X_FOG fog_color, FALSE, fog_near, fog_far

// Draw Ground
X_CULLMODE 1 // Set to 1 becaus we don't need to draw the underneath of the ground
X_MOVEMENT 0,0,0
X_ROTATION 0,0,0,0
X_SETTEXTURE 1, -1
X_DRAWOBJ 1, 0

// Setup Lighting
X_AMBIENT_LT 2, light_color

// =============================================================
// Setup 2D
// =============================================================

X_MAKE2D

// Get FPS
fps_time = GETTIMERALL()
IF start_time + 1000 > fps_time
INC fps_counter,1
ELSE
start_time = GETTIMERALL()
fps_out = fps_counter
fps_counter = 0
ENDIF

// Print out some info
PRINT "3rd Person Camera by Synthetic o.O",0,0
PRINT "FPS                     : " + fps_out,0,20
PRINT "Delay Per Frame         : " + frame_delay,0,40
PRINT "Mouse Look X            : " + cam_mouse_x,0,60
PRINT "Mouse Look Y            : " + cam_mouse_y,0,80
PRINT "Camera Position X       : " + cam_final_pos_x,0,100
PRINT "Camera Position Y       : " + cam_final_pos_y,0,120
PRINT "Camera Position Z       : " + cam_final_pos_z,0,140
PRINT "Camera View X           : " + cam_final_view_x,0,160
PRINT "Camera View Y           : " + cam_final_view_y,0,180
PRINT "Camera View Z           : " + cam_final_view_z,0,200
PRINT "Character Direction     : " + char_direction,0,220
PRINT "Character Position X    : " + char_pos_x,0,240
PRINT "Character Position Y    : " + char_pos_y,0,260
PRINT "Character Position Z    : " + char_pos_z,0,280

// Setup mouse cursor
MOUSESTATE mouse_x,mouse_y,nil,nil // Get mouse x,y coordinates,
IF MOUSEAXIS(4) // Show movement cursor when right mouse button is held.
DRAWSPRITE 3,mouse_x,mouse_y
ELSE // Show standard cursor when right mouse button isn't held.
DRAWSPRITE 2,mouse_x,mouse_y
ENDIF
SHOWSCREEN


// To get our time delay for calculating timer based movement we need to keep track of two variables.
        // old_time and current_time. old_time is the time it was before the program completed a loop. current_time
// is the time it is now after the loop has gone through. To get the difference, it's just simple subtraction.
// current_time - old_time which gives us how long in milliseconds it takes to process a full program loop.
// Then by dividing this by 1000 (1000 ms = 1 second) we are telling it to give us a number we can use
// to calculate our movement compensation needed for every second gone by.

// Setup counters for time based movement
old_time = current_time
    current_time = GETTIMERALL()
    frame_delay = current_time - old_time
    frame_delay_amount = frame_delay / 1000.0
WEND


// =============================================================
// Create Land Map Function
// =============================================================
FUNCTION DrawMap: id_ddd, ft
LOCAL sc, mvx, mvy, x, y, col

col=RGB(255,255,255)

sc = 200
mvx = sc * MAXX / 2
mvy = sc * MAXY / 2
X_AUTONORMALS 2
X_OBJSTART id_ddd
FOR x=0 TO MAXX-1
X_OBJNEWGROUP
X_OBJADDVERTEX x*sc-mvx, Height[x][1], 1*sc-mvy, ft*x, ft, col
FOR y=1 TO MAXY-1
X_OBJADDVERTEX (x+1)*sc-mvx, Height[x+1][y], y*sc-mvy, ft*(x+1), ft*y, col
X_OBJADDVERTEX x*sc-mvx, Height[x][y+1], (y+1)*sc-mvy, ft*x, ft*(y+1), col
NEXT
X_OBJADDVERTEX (x+1)*sc-mvx, Height[x+1][y], y*sc-mvy, ft*(x+1), ft*y, col
NEXT
X_OBJEND
ENDFUNCTION


[attachment deleted by admin]
"Impossible is a word people use to make themselves feel better when they quit."

My AMXMODX plugins for Day of Defeat 1.3 can be found here.

Moru

Very nice documented, thanks :-)

Hemlos

Very nice example. Im putting it in the 3d section.


Quote from: Synthetic on 2008-Jun-09
I do plan on adding more features later and I'm always open to suggestions and optimizations.

Add a command list in your message before the code.
ie
Quote
Commands:
blah
blah
blah


Code (Code:) Select
[code=glbasic][/code]
Bing ChatGpt is pretty smart :O