I am trying to achieve a Mass Effect style over-the-shoulder camera in my game. Here is the code I have to position the camera over the player character object's "shoulder":
dbRotateObject (Current_Level.Character[Local_Loops].Mesh, 0, dbObjectAngleY(Current_Level.Character[Local_Loops].Mesh) + (dbMouseMoveX() * .25), 0);
if (dbKeyState(17) == 1) // Forward
{
dbMoveObject (Current_Level.Character[Local_Loops].Mesh, .07);
}
if (dbKeyState(31) == 1) // Backward
{
dbMoveObject (Current_Level.Character[Local_Loops].Mesh, -.07);
}
if (dbKeyState(30) == 1) // Left
{
dbRotateObject (Current_Level.Character[Local_Loops].Mesh, 0, dbObjectAngleY(Current_Level.Character[Local_Loops].Mesh) - 90, 0);
dbMoveObject (Current_Level.Character[Local_Loops].Mesh, .07);
dbRotateObject (Current_Level.Character[Local_Loops].Mesh, 0, dbObjectAngleY(Current_Level.Character[Local_Loops].Mesh) + 90, 0);
}
if (dbKeyState(32) == 1) // Right
{
dbRotateObject (Current_Level.Character[Local_Loops].Mesh, 0, dbObjectAngleY(Current_Level.Character[Local_Loops].Mesh) + 90, 0);
dbMoveObject (Current_Level.Character[Local_Loops].Mesh, .07);
dbRotateObject (Current_Level.Character[Local_Loops].Mesh, 0, dbObjectAngleY(Current_Level.Character[Local_Loops].Mesh) - 90, 0);
}
dbPositionCamera ( dbObjectPositionX(Current_Level.Character[Local_Loops].Mesh),
dbObjectPositionY(Current_Level.Character[Local_Loops].Mesh) + 1.4,
dbObjectPositionZ(Current_Level.Character[Local_Loops].Mesh));
dbRotateCamera (dbCameraAngleX() + (dbMouseMoveY() * .25), dbObjectAngleY(Current_Level.Character[Local_Loops].Mesh), 0);
dbMoveCamera (-2.35);
dbRotateCamera (dbCameraAngleX(), dbCameraAngleY() + 90, 0);
dbMoveCamera (.65);
dbRotateCamera (dbCameraAngleX(), dbObjectAngleY(Current_Level.Character[Local_Loops].Mesh), 0);
dbPositionObject (Camera_Collision_Sphere, dbCameraPositionX(), dbCameraPositionY(), dbCameraPositionZ());
dbRotateObject(Camera_Collision_Sphere, dbCameraAngleX(), dbCameraAngleY(), dbCameraAngleZ());
And this is the code for the player character object's sliding collision:
// Character Collision
if (SC_SphereSlideGroup (1, Initial_X, Initial_Y, Initial_Z, dbObjectPositionX(Current_Level.Character[Local_Loops].Mesh),
dbObjectPositionY(Current_Level.Character[Local_Loops].Mesh), dbObjectPositionZ(Current_Level.Character[Local_Loops].Mesh),
.5, Current_Level.Character[Local_Loops].Mesh) > 0)
{
dbPositionObject (Current_Level.Character[Local_Loops].Mesh, SC_GetCollisionSlideX(), SC_GetCollisionSlideY(), SC_GetCollisionSlideZ());
SC_UpdateObject (Current_Level.Character[Local_Loops].Mesh);
}
That all works beautifully. However, there was nothing to stop the camera from passing through the walls of the level, so you could see right through them. I added this camera collision code to prevent that:
// Camera Collision
if (SC_SphereSlideGroup (1, Camera_Initial_X, Camera_Initial_Y, Camera_Initial_Z, dbObjectPositionX(Camera_Collision_Sphere),
dbObjectPositionY(Camera_Collision_Sphere), dbObjectPositionZ(Camera_Collision_Sphere), 1.5, Camera_Collision_Sphere) > 0)
{
dbPositionObject (Camera_Collision_Sphere, SC_GetCollisionSlideX(), SC_GetCollisionSlideY(), SC_GetCollisionSlideZ());
SC_UpdateObject (Camera_Collision_Sphere);
dbPositionCamera (dbObjectPositionX(Camera_Collision_Sphere), dbObjectPositionY(Camera_Collision_Sphere), dbObjectPositionZ(Camera_Collision_Sphere));
}
That sort of works, but not very well. The camera often jumps around weirdly and gets stuck on the walls.
There has to be a better way of doing this. Can anyone help?
Here is the entire code, if that helps. It's big, and it's got a lot of other completely unrelated stuff, but it still may be helpful.
#include "DarkGDK.h"
#include "SC_Collision.h"
//-------------------------------------------------------------------------
// Global variables
//-------------------------------------------------------------------------
int ObjID = 1;
int TexID = 1;
int SndID = 1;
int Game_Mode; // 0 = main menu, 1 = loading, 2 = gameplay, 3 = cutscene, 4 = pause menu
//-------------------------------------------------------------------------
// Structures
//-------------------------------------------------------------------------
// Structure for human characters
struct Char_Struct
{
int Mesh; // .X mesh file for the character
int AI; // 0 = Player, 1 = Ally, 2 = Neutral, 3 = Enemy
float Position[3]; // XYZ coordinate of the character's initial position in the level
float Rotation; // The character's initial rotation in the level
float Health_Max; // If max <= 0 the character is dead right from the start and therefore
float Health_Current; // only exists as a corpse, otherwise he/she will die when current <= 0
int Alert_Mode; // 0 = undetected, 1 = alert, 2 = searching, 3 = detected
};
// Structure for cover objects
struct Cover_Struct
{
int Mesh; // .X mesh file for the cover object
float Position[3]; // XYZ coordinate for the cover object's position
float Rotation; // Rotation of the cover object
float Heath_Max; // If max < 0 the cover object is indestructible,
float Health_Current; // otherwise it will be destroyed when current <= 0
};
// Structure for levels
struct Level
{
int Mesh; // .X mesh file for the level
float End_Position[3]; // A cube that ends the level when
float End_Scale[3]; // entered by the player
struct Cover_Struct Cover[1000]; // Array of all the cover objects in the level
struct Char_Struct Character[1000]; // Array of all the human characters in the level
};
//-------------------------------------------------------------------------
// Loading functions
//-------------------------------------------------------------------------
// Function to load 3D objects
int Load_3D (char Local_Filepath[])
{
int Local_ID;
// Generate an ID number for the mesh
Local_ID = ObjID;
++ObjID;
// Load the mesh
dbLoadObject (Local_Filepath, Local_ID);
// Setup collision
SC_SetupObject (Local_ID, 1, 0);
return Local_ID;
}
//-------------------------------------------------------------------------
// Structure Functions
//-------------------------------------------------------------------------
struct Char_Struct Create_Character (char Local_Mesh[], int Local_AI, float Local_X, float Local_Y, float Local_Z, float Local_Rot, float Local_HP)
{
struct Char_Struct Local_Character;
// Load character mesh
Local_Character.Mesh = Load_3D (Local_Mesh);
// Set AI type
Local_Character.AI = Local_AI;
// Set starting position and rotation
Local_Character.Position[0] = Local_X;
Local_Character.Position[1] = Local_Y;
Local_Character.Position[2] = Local_Z;
Local_Character.Rotation = Local_Rot;
// Set health
Local_Character.Health_Max = Local_HP;
Local_Character.Health_Current = Local_Character.Health_Max;
// Position and rotate character
dbPositionObject (Local_Character.Mesh, Local_Character.Position[0], Local_Character.Position[1], Local_Character.Position[2]);
dbRotateObject (Local_Character.Mesh, 0, Local_Character.Rotation, 0);
return Local_Character;
}
// Function to load the current level
struct Level Load_Level (char Local_Mesh[], char Local_Player_Mesh[], float Local_X, float Local_Y, float Local_Z, float Local_Rot)
{
struct Level Local_Level;
struct Char_Struct Local_Character;
// Load world mesh
Local_Level.Mesh = Load_3D (Local_Mesh);
// Create the player character
Local_Level.Character[0] = Create_Character (Local_Player_Mesh, 0, Local_X, Local_Z, Local_Y, Local_Rot, 10);
return Local_Level;
}
// End_Level
//-------------------------------------------------------------------------
// Main function
//-------------------------------------------------------------------------
void DarkGDK (void)
{
//-------------------------------------------------------------------------
// Initialization
//-------------------------------------------------------------------------
dbSyncOn ();
dbSetDisplayMode (dbDesktopWidth (), dbDesktopHeight(), 32);
dbSetWindowOff ();
dbAutoCamOff ();
dbHideMouse ();
dbColorBackdrop (dbRGB(0, 0, 0));
SC_Start();
struct Level Current_Level;
int Camera_Collision_Sphere;
float Camera_Initial_X;
float Camera_Initial_Y;
float Camera_Initial_Z;
// Main Loop
for (int Main_Loop = 1; Main_Loop > 0;)
{
switch (Game_Mode)
{
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Main Menu
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
case 0:
Game_Mode = 1; // TEMPORARY
break;
//-------------------------------------------------------------------------
// Load Level
//-------------------------------------------------------------------------
case 1:
// Determine which level to load
// Create the level
Current_Level = Load_Level ("Data/3D/World/TestLevel/mesh.x", "Data/3D/Characters/TestPlayer/mesh.x", -45.00, -32.00, 0.00, 90.00);
// Create camera collision object
Camera_Collision_Sphere = Load_3D ("Data/3D/Miscellaneous/CameraCollisionSphere/mesh.x");
// Proceed to gameplay
Game_Mode = 2;
break;
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Gameplay
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
case 2:
// Initial position of the character
Camera_Initial_X = dbObjectPositionX(Camera_Collision_Sphere);
Camera_Initial_Y = dbObjectPositionY(Camera_Collision_Sphere);
Camera_Initial_Z = dbObjectPositionZ(Camera_Collision_Sphere);
// If escape is pressed, go to pause menu
if (dbKeyState(1) == 1)
{
Game_Mode = 4;
}
// If the player is in the end area, execute Level_End
// Character Loop
for (int Local_Loops = 0; dbObjectExist(Current_Level.Character[Local_Loops].Mesh) == 1; ++Local_Loops)
{
// Initial position of the character
float Initial_X = dbObjectPositionX(Current_Level.Character[Local_Loops].Mesh);
float Initial_Y = dbObjectPositionY(Current_Level.Character[Local_Loops].Mesh);
float Initial_Z = dbObjectPositionZ(Current_Level.Character[Local_Loops].Mesh);
// Player controls
switch (Current_Level.Character[Local_Loops].AI)
{
case 0:
dbRotateObject (Current_Level.Character[Local_Loops].Mesh, 0, dbObjectAngleY(Current_Level.Character[Local_Loops].Mesh) + (dbMouseMoveX() * .25), 0);
if (dbKeyState(17) == 1) // Forward
{
dbMoveObject (Current_Level.Character[Local_Loops].Mesh, .07);
}
if (dbKeyState(31) == 1) // Backward
{
dbMoveObject (Current_Level.Character[Local_Loops].Mesh, -.07);
}
if (dbKeyState(30) == 1) // Left
{
dbRotateObject (Current_Level.Character[Local_Loops].Mesh, 0, dbObjectAngleY(Current_Level.Character[Local_Loops].Mesh) - 90, 0);
dbMoveObject (Current_Level.Character[Local_Loops].Mesh, .07);
dbRotateObject (Current_Level.Character[Local_Loops].Mesh, 0, dbObjectAngleY(Current_Level.Character[Local_Loops].Mesh) + 90, 0);
}
if (dbKeyState(32) == 1) // Right
{
dbRotateObject (Current_Level.Character[Local_Loops].Mesh, 0, dbObjectAngleY(Current_Level.Character[Local_Loops].Mesh) + 90, 0);
dbMoveObject (Current_Level.Character[Local_Loops].Mesh, .07);
dbRotateObject (Current_Level.Character[Local_Loops].Mesh, 0, dbObjectAngleY(Current_Level.Character[Local_Loops].Mesh) - 90, 0);
}
dbPositionCamera ( dbObjectPositionX(Current_Level.Character[Local_Loops].Mesh),
dbObjectPositionY(Current_Level.Character[Local_Loops].Mesh) + 1.4,
dbObjectPositionZ(Current_Level.Character[Local_Loops].Mesh));
dbRotateCamera (dbCameraAngleX() + (dbMouseMoveY() * .25), dbObjectAngleY(Current_Level.Character[Local_Loops].Mesh), 0);
dbMoveCamera (-2.35);
dbRotateCamera (dbCameraAngleX(), dbCameraAngleY() + 90, 0);
dbMoveCamera (.65);
dbRotateCamera (dbCameraAngleX(), dbObjectAngleY(Current_Level.Character[Local_Loops].Mesh), 0);
dbPositionObject (Camera_Collision_Sphere, dbCameraPositionX(), dbCameraPositionY(), dbCameraPositionZ());
dbRotateObject(Camera_Collision_Sphere, dbCameraAngleX(), dbCameraAngleY(), dbCameraAngleZ());
break;
// Ally AI
case 1:
//TEMPORARY
break;
// Neutral AI
case 2:
//TEMPORARY
break;
// Enemy AI
case 3:
//TEMPORARY
break;
}
// Character Collision
if (SC_SphereSlideGroup (1, Initial_X, Initial_Y, Initial_Z, dbObjectPositionX(Current_Level.Character[Local_Loops].Mesh),
dbObjectPositionY(Current_Level.Character[Local_Loops].Mesh), dbObjectPositionZ(Current_Level.Character[Local_Loops].Mesh),
.5, Current_Level.Character[Local_Loops].Mesh) > 0)
{
dbPositionObject (Current_Level.Character[Local_Loops].Mesh, SC_GetCollisionSlideX(), SC_GetCollisionSlideY(), SC_GetCollisionSlideZ());
SC_UpdateObject (Current_Level.Character[Local_Loops].Mesh);
}
}
// Camera Collision
if (SC_SphereSlideGroup (1, Camera_Initial_X, Camera_Initial_Y, Camera_Initial_Z, dbObjectPositionX(Camera_Collision_Sphere),
dbObjectPositionY(Camera_Collision_Sphere), dbObjectPositionZ(Camera_Collision_Sphere), 1.5, Camera_Collision_Sphere) > 0)
{
dbPositionObject (Camera_Collision_Sphere, SC_GetCollisionSlideX(), SC_GetCollisionSlideY(), SC_GetCollisionSlideZ());
SC_UpdateObject (Camera_Collision_Sphere);
dbPositionCamera (dbObjectPositionX(Camera_Collision_Sphere), dbObjectPositionY(Camera_Collision_Sphere), dbObjectPositionZ(Camera_Collision_Sphere));
}
break;
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Pause Menu
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
case 4:
Main_Loop = 0; // TEMPORARY
}
//-------------------------------------------------------------------------
// Update Screen
//-------------------------------------------------------------------------
dbSync ();
}
return;
}
Hail to the king baby
I love Evil Dead.