From ae80c7a5bfc1a68ca4a1bed3b5ac12847aa6401c Mon Sep 17 00:00:00 2001 From: Kenneth van Ewijk Date: Sat, 28 May 2016 16:04:44 +0200 Subject: [PATCH] Add health/xp/level interface --- CrystalPoint.cpp | 22 +++----- CrystalPoint.h | 3 +- CrystalPoint.vcxproj | 4 ++ CrystalPoint.vcxproj.filters | 18 +++++-- Cursor.cpp | 58 +++++++++++++++++++++ Cursor.h | 24 +++++++++ HeightMap.cpp | 2 +- Interface.cpp | 94 +++++++++++++++++++++++++++++++++++ Interface.h | 11 ++++ Main.cpp | 20 +++++--- Player.cpp | 3 ++ Player.h | 3 ++ World.cpp | 60 ++++++++++++++++------ World.h | 18 ++++--- worlds/small.json | 3 +- worlds/small.png | Bin 2229 -> 1957 bytes 16 files changed, 289 insertions(+), 54 deletions(-) create mode 100644 Cursor.cpp create mode 100644 Cursor.h create mode 100644 Interface.cpp create mode 100644 Interface.h diff --git a/CrystalPoint.cpp b/CrystalPoint.cpp index 45c6cd5..308696a 100644 --- a/CrystalPoint.cpp +++ b/CrystalPoint.cpp @@ -6,10 +6,14 @@ #include "WorldHandler.h" #include "Player.h" +int CrystalPoint::width = 0; +int CrystalPoint::height = 0; + void CrystalPoint::init() { player = Player::getInstance(); worldhandler = WorldHandler::getInstance(); + //cursor = Cursor::getInstance(); lastFrameTime = 0; @@ -34,23 +38,8 @@ void CrystalPoint::draw() glLoadIdentity(); worldhandler->draw(); - - //Draw Cursor - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0,width, height,0,-10,10); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glDisable(GL_LIGHTING); - glDisable(GL_DEPTH_TEST); - glColor4f(1, cos(glutGet(GLUT_ELAPSED_TIME) / 1000.0f), sin(glutGet(GLUT_ELAPSED_TIME) / 1000.0f), 1); - - glBegin(GL_TRIANGLES); - glVertex2f(mousePosition.x, mousePosition.y); - glVertex2f(mousePosition.x+15, mousePosition.y+15); - glVertex2f(mousePosition.x+5, mousePosition.y+20); - glEnd(); + //cursor->draw(); glutSwapBuffers(); } @@ -96,6 +85,7 @@ void CrystalPoint::update() worldhandler->update(deltaTime); mousePosition = mousePosition + mouseOffset; + //cursor->update(mousePosition); mouseOffset = Vec2f(0, 0); prevKeyboardState = keyboardState; diff --git a/CrystalPoint.h b/CrystalPoint.h index fef6a36..da4a3b8 100644 --- a/CrystalPoint.h +++ b/CrystalPoint.h @@ -24,12 +24,11 @@ public: WorldHandler* worldhandler; Player* player; - int width, height; + static int width, height; KeyboardState keyboardState; KeyboardState prevKeyboardState; Vec2f mouseOffset; - Vec2f mousePosition; float lastFrameTime; diff --git a/CrystalPoint.vcxproj b/CrystalPoint.vcxproj index 97671cc..4056f40 100644 --- a/CrystalPoint.vcxproj +++ b/CrystalPoint.vcxproj @@ -152,9 +152,11 @@ + + @@ -167,9 +169,11 @@ + + diff --git a/CrystalPoint.vcxproj.filters b/CrystalPoint.vcxproj.filters index 43a3b3d..2430d22 100644 --- a/CrystalPoint.vcxproj.filters +++ b/CrystalPoint.vcxproj.filters @@ -63,6 +63,12 @@ Source Files + + Source Files + + + Source Files\World + @@ -77,9 +83,6 @@ Header Files - - Header Files - Header Files @@ -107,6 +110,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + diff --git a/Cursor.cpp b/Cursor.cpp new file mode 100644 index 0000000..69895a3 --- /dev/null +++ b/Cursor.cpp @@ -0,0 +1,58 @@ +#include "Cursor.h" +#include +#include +#include "CrystalPoint.h" + +Cursor* Cursor::instance = NULL; + +Cursor::Cursor() +{ + enabled = false; +} + +Cursor::~Cursor() +{ +} + +Cursor* Cursor::getInstance(void) +{ + if (instance == nullptr) + instance = new Cursor(); + + return instance; +} + +void Cursor::enable(bool enable) +{ + enabled = enable; +} + +bool Cursor::isEnabled(void) +{ + return enabled; +} + +void Cursor::draw(void) +{ + //Draw Cursor + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, CrystalPoint::width, CrystalPoint::height, 0, -10, 10); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glDisable(GL_LIGHTING); + glDisable(GL_DEPTH_TEST); + glColor4f(1, cos(glutGet(GLUT_ELAPSED_TIME) / 1000.0f), sin(glutGet(GLUT_ELAPSED_TIME) / 1000.0f), 1); + + glBegin(GL_TRIANGLES); + glVertex2f(mousePosition.x, mousePosition.y); + glVertex2f(mousePosition.x + 15, mousePosition.y + 15); + glVertex2f(mousePosition.x + 5, mousePosition.y + 20); + glEnd(); +} + +void Cursor::update(Vec2f newPosition) +{ + mousePosition = newPosition; +} diff --git a/Cursor.h b/Cursor.h new file mode 100644 index 0000000..78764f0 --- /dev/null +++ b/Cursor.h @@ -0,0 +1,24 @@ +#pragma once +#include "Vector.h" + +class Cursor +{ +private: + Cursor(); + + static Cursor* instance; + bool enabled; + Vec2f mousePosition; +public: + + ~Cursor(); + + static Cursor* getInstance(void); + + void enable(bool enable); + bool isEnabled(void); + + void draw(void); + void update(Vec2f newPosition); +}; + diff --git a/HeightMap.cpp b/HeightMap.cpp index 5494253..79ebb79 100644 --- a/HeightMap.cpp +++ b/HeightMap.cpp @@ -39,7 +39,7 @@ HeightMap::HeightMap(const std::string &file, World* world) if (valueAt(x, y, GREEN) > 0) { - world->addLevelObject(new LevelObject(world->getObjectFromValue(valueAt(x, y, GREEN)), Vec3f(x, heightAt(x, y), y), Vec3f(0, rand()%360, 0), 1, true)); + world->addLevelObject(new LevelObject(world->getObjectFromValue(valueAt(x, y, GREEN)).first, Vec3f(x, heightAt(x, y), y), Vec3f(0, rand()%360, 0), 1, world->getObjectFromValue(valueAt(x, y, GREEN)).second)); } Vec3f normal = ca.cross(ba); diff --git a/Interface.cpp b/Interface.cpp new file mode 100644 index 0000000..85d2778 --- /dev/null +++ b/Interface.cpp @@ -0,0 +1,94 @@ +#include "Interface.h" +#include +#include "CrystalPoint.h" + +#include + +#include "Player.h" + +//Prototype +void glutBitmapString(std::string str, int x, int y); + +Interface::Interface() +{ +} + + +Interface::~Interface() +{ +} + +void Interface::draw() +{ + Player* player = Player::getInstance(); + + //Switch view to Ortho + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, 1000, 1000, 0, -10, 10); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glDisable(GL_LIGHTING); + glDisable(GL_DEPTH_TEST); + glDisable(GL_TEXTURE_2D); + + //Draw interface + + //Health bar + glBegin(GL_QUADS); + glColor4f(0,0,0, 1.0); + glVertex2f(250, 980); + glVertex2f(250, 965); + glVertex2f(750, 965); + glVertex2f(750, 980); + glEnd(); + + glBegin(GL_QUADS); + glColor4f(1.0f, 0.1f, 0.1f, 1.0); + glVertex2f(250, 980); + glVertex2f(250, 965); + + glColor4f(1.0f, 0.5f, 0.5f, 1.0); + glVertex2f(250 + (player->health / 100 * 500), 965); + glVertex2f(250 + (player->health / 100 * 500), 980); + glEnd(); + + //XP bar + glBegin(GL_QUADS); + glColor4f(0, 0, 0, 1.0); + glVertex2f(250, 950); + glVertex2f(250, 935); + glVertex2f(750, 935); + glVertex2f(750, 950); + glEnd(); + + glBegin(GL_QUADS); + glColor4f(1.0f, 1.0f, 0.1f, 1.0); + glVertex2f(250, 950); + glVertex2f(250, 935); + + glColor4f(1.0f, 1.0f, 0.5f, 1.0); + glVertex2f(250 + (player->xp / 100 * 500), 935); + glVertex2f(250 + (player->xp / 100 * 500), 950); + glEnd(); + + //Text: level + glColor4f(1.0f, 1.0f, 0.1f, 1.0); + glutBitmapString("Level: " + std::to_string(player->level), 490, 900); + +} + +void Interface::update(float deltaTime) +{ + +} + +void glutBitmapString(std::string str, int x, int y) +{ + glRasterPos2f(x, y); + for (int i = 0; i < str.size(); i++) + { + glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, str[i]); + } +} \ No newline at end of file diff --git a/Interface.h b/Interface.h new file mode 100644 index 0000000..ddc756e --- /dev/null +++ b/Interface.h @@ -0,0 +1,11 @@ +#pragma once +class Interface +{ +public: + Interface(); + ~Interface(); + + void draw(void); + void update(float deltaTime); +}; + diff --git a/Main.cpp b/Main.cpp index b385658..d89aa26 100644 --- a/Main.cpp +++ b/Main.cpp @@ -21,7 +21,7 @@ int main(int argc, char* argv[]) glutDisplayFunc([]() { app->draw(); } ); glutIdleFunc([]() { app->update(); } ); - glutReshapeFunc([](int w, int h) { app->width = w; app->height = h; glViewport(0, 0, w, h); }); + glutReshapeFunc([](int w, int h) { CrystalPoint::width = w; CrystalPoint::height = h; glViewport(0, 0, w, h); }); //Keyboard glutKeyboardFunc([](unsigned char c, int, int) { app->keyboardState.keys[c] = true; }); @@ -29,8 +29,7 @@ int main(int argc, char* argv[]) glutSpecialFunc([](int c, int, int) { app->keyboardState.special[c] = true; }); glutSpecialUpFunc([](int c, int, int) { app->keyboardState.special[c] = false; }); - //Mouse - glutPassiveMotionFunc([](int x, int y) + auto mousemotion = [](int x, int y) { if (justMoved) { @@ -41,11 +40,18 @@ int main(int argc, char* argv[]) int dy = y - app->height / 2; if ((dx != 0 || dy != 0) && abs(dx) < 400 && abs(dy) < 400) { - app->mouseOffset = app->mouseOffset + Vec2f(dx,dy); + app->mouseOffset = app->mouseOffset + Vec2f(dx, dy); glutWarpPointer(app->width / 2, app->height / 2); justMoved = true; } - }); + }; + + //Mouse + glutPassiveMotionFunc(mousemotion); + glutMotionFunc(mousemotion); + + CrystalPoint::height = GLUT_WINDOW_HEIGHT; + CrystalPoint::width = GLUT_WINDOW_WIDTH; glutMainLoop(); return 0; @@ -57,7 +63,7 @@ void configureOpenGL() glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); glutInitWindowSize(800, 600); glutCreateWindow("Crystal Point"); - //glutFullScreen(); + glutFullScreen(); //Depth testing glEnable(GL_DEPTH_TEST); @@ -86,5 +92,5 @@ void configureOpenGL() glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); - glutSetCursor(GLUT_CURSOR_CROSSHAIR); + glutSetCursor(GLUT_CURSOR_NONE); } \ No newline at end of file diff --git a/Player.cpp b/Player.cpp index dcb172e..b43bd82 100644 --- a/Player.cpp +++ b/Player.cpp @@ -8,6 +8,9 @@ Player* Player::instance = NULL; Player::Player() { speed = 10; + health = 50; + xp = 75; + level = 10; } Player* Player::getInstance() diff --git a/Player.h b/Player.h index d9a2a8f..18d3fb8 100644 --- a/Player.h +++ b/Player.h @@ -23,6 +23,9 @@ public: Model* leftWeapon; Model* rightWeapon; + float health; + float xp; + int level; float speed; }; \ No newline at end of file diff --git a/World.cpp b/World.cpp index 5bbc272..c3a9500 100644 --- a/World.cpp +++ b/World.cpp @@ -3,13 +3,19 @@ #include "Entity.h" #include "json.h" #include "Model.h" +#include "CrystalPoint.h" #include #include World::World(const std::string &fileName) { + //Store player instance player = Player::getInstance(); + //Create the interface + interface = new Interface(); + + //Open world json file std::ifstream file(fileName); if(!file.is_open()) std::cout<<"Error, can't open world file - " << fileName << "\n"; @@ -30,60 +36,81 @@ World::World(const std::string &fileName) //Load object templates for (auto objt : v["world"]["object-templates"]) { - objecttemplates.push_back(std::pair(objt["color"], objt["file"])); + //collision + bool cancollide = true; + if (!objt["collision"].isNull()) + cancollide = objt["collision"].asBool(); + + objecttemplates.push_back(std::pair>(objt["color"], std::pair(objt["file"], cancollide))); } + //Generate heightmap for this world heightmap = new HeightMap(v["world"]["heightmap"].asString(), this); + //Map different texture to heightmap if available if(!v["world"]["texture"].isNull()) heightmap->SetTexture(v["world"]["texture"].asString()); + //Set player starting position player->position.x = v["player"]["startposition"][0].asFloat(); player->position.y = v["player"]["startposition"][1].asFloat(); player->position.z = v["player"]["startposition"][2].asFloat(); - + //Load and place objects into world for (auto object : v["objects"]) { + //Collision bool hasCollision = true; if (!object["collide"].isNull()) hasCollision = object["collide"].asBool(); + //Rotation Vec3f rotation(0, 0, 0); if(!object["rot"].isNull()) rotation = Vec3f(object["rot"][0].asFloat(), object["rot"][1].asFloat(), object["rot"][2].asFloat()); + //Scale float scale = 1; if (!object["scale"].isNull()) scale = object["scale"].asFloat(); + //Position if (object["pos"].isNull()) std::cout << "Invalid world file: objects pos - " << fileName << "\n"; + + //File + if (object["file"].isNull()) + std::cout << "Invalid world file: objects file - " << fileName << "\n"; + //Create Vec3f position(object["pos"][0].asFloat(), object["pos"][1].asFloat(), object["pos"][2].asFloat()); - entities.push_back(new LevelObject(object["file"], position, rotation, scale, hasCollision)); + entities.push_back(new LevelObject(object["file"].asString(), position, rotation, scale, hasCollision)); } - //Enemies + //Load and place enemies into world for (auto e : v["enemies"]) { - std::string fileName = ""; - if (!e["file"].isNull()) - fileName = e["file"].asString(); - - Vec3f position(0, 0, 0); - if (!e["pos"].isNull()) - position = Vec3f(e["pos"][0].asFloat(), e["pos"][1].asFloat(), e["pos"][2].asFloat()); - + //Rotation Vec3f rotation(0, 0, 0); if (!e["rot"].isNull()) rotation = Vec3f(e["rot"][0].asFloat(), e["rot"][1].asFloat(), e["rot"][2].asFloat()); + //Scale float scale = 1.0f; if (!e["scale"].isNull()) scale = e["scale"].asFloat(); - enemies.push_back(new Enemy(fileName, position, rotation, scale)); + //Position + if (e["pos"].isNull()) + std::cout << "Invalid world file: enemies pos - " << fileName << "\n"; + + //File + if (e["file"].isNull()) + std::cout << "Invalid world file: enemies file - " << fileName << "\n"; + + //Create + Vec3f position(e["pos"][0].asFloat(), e["pos"][1].asFloat(), e["pos"][2].asFloat()); + enemies.push_back(new Enemy(e["file"].asString(), position, rotation, scale)); } } @@ -94,7 +121,7 @@ World::~World() delete heightmap; } -std::string World::getObjectFromValue(int val) +std::pair World::getObjectFromValue(int val) { for (auto i : objecttemplates) { @@ -116,10 +143,9 @@ void World::draw() float lightPosition[4] = { 0, 2, 1, 0 }; glLightfv(GL_LIGHT0, GL_POSITION, lightPosition); - float lightAmbient[4] = { 0.5, 0.5, 0.5, 1 }; + float lightAmbient[4] = { 0.2, 0.2, 0.2, 1 }; glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient); - glColor4f(1.0f, 1.0f, 1.0f, 1.0f); heightmap->Draw(); for (auto &enemy : enemies) @@ -127,6 +153,8 @@ void World::draw() for (auto &entity : entities) entity->draw(); + + interface->draw(); } void World::update(float elapsedTime) diff --git a/World.h b/World.h index de9c803..649e2d4 100644 --- a/World.h +++ b/World.h @@ -5,28 +5,30 @@ #include "Player.h" #include "Enemy.h" #include "LevelObject.h" +#include "Interface.h" class Entity; class World { private: - std::vector> objecttemplates; -public: - World(const std::string &fileName); - ~World(); - - std::vector entities; - std::vector enemies; + std::vector>> objecttemplates; Player* player; HeightMap* heightmap; + Interface* interface; + + std::vector entities; + std::vector enemies; +public: + World(const std::string &fileName); + ~World(); void draw(); void update(float elapsedTime); bool isPlayerPositionValid(); float getHeight(float x, float y); void addLevelObject(LevelObject* obj); - std::string getObjectFromValue(int i); + std::pair getObjectFromValue(int i); }; diff --git a/worlds/small.json b/worlds/small.json index d548c08..466db7c 100644 --- a/worlds/small.json +++ b/worlds/small.json @@ -4,7 +4,8 @@ "object-templates": [ { "color":100, - "file": "models/boom/Boom.obj" + "file": "models/boom/Boom.obj", + "collision": false } ] }, diff --git a/worlds/small.png b/worlds/small.png index 72f885bc94e566db8a35c5670e8d363b8da49b79..714b7cdc1acd6a88e056638b47c0b8e8c38a3bd0 100644 GIT binary patch delta 1920 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 L#P26xdfxv4%SPB+