Jump to content
• Advertisement
• entries
7
• comments
5
• views
1360

### About this blog

Join my in my painful journey re-familiarizing myself with C++ code while learning DirectX11.

## Dungeon Crawler Challenge - Tile Movement!

Hello all, since the Frogger Challenge I have been hard at work doing some cleanup in my "engine" and adding a few features that were missing from the first challenge. Specifically, I was able to add add in a state machine as well as figured out how to use SFML for poller based input. I decided that I didn't have enough of one type of art to fully flesh out the graphics of a dungeon crawler, they were all a bit mismatched and I decided to forego frankensteining it together, so I went over to Oryx Design Labs and got their Ultimate Fantasy Tileset! One of my first major hurdles was that I decided I wanted to use tile based movement rather than the free movement that I used in the Frogger Challenge. I'm trying to slightly mimic the kind of motion i see in Sproggiwood and Paper Dungeons Crawler. I visited the forums for a while and ended up coming up with my own flavor of tile based movement that I believe at least is a close match to what I'm seeing in these games. I wanted diagonal movement, but I wanted everything locked to the tiles. and I think I have accomplished that at this time! See Below: 2018-12-24_00-10-03.mp4 This was more of a task than I thought it would be, but here is the code that makes it work, I'm definitely open to some suggestions here!   Firstly, input! if (sf::Keyboard::isKeyPressed(sf::Keyboard::W) && sf::Keyboard::isKeyPressed(sf::Keyboard::D)) { if(gameobjects[0]->getPhysics()->inMotion == false) { gameobjects[0]->setVelocity(XMFLOAT3{ float(.025) * dt,float(.025) * dt, 0 }); gameobjects[0]->nextmapcoords.x = gameobjects[0]->mapcoords.x + 1; gameobjects[0]->nextmapcoords.y = gameobjects[0]->mapcoords.y + 1; } } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::W) && sf::Keyboard::isKeyPressed(sf::Keyboard::A)) { if (gameobjects[0]->getPhysics()->inMotion == false) { gameobjects[0]->setVelocity(XMFLOAT3{ float(-.025) * dt,float(.025) * dt, 0 }); gameobjects[0]->nextmapcoords.x = gameobjects[0]->mapcoords.x - 1; gameobjects[0]->nextmapcoords.y = gameobjects[0]->mapcoords.y + 1; } } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::S) && sf::Keyboard::isKeyPressed(sf::Keyboard::D)) { if (gameobjects[0]->getPhysics()->inMotion == false) { gameobjects[0]->setVelocity(XMFLOAT3{ float(.025) * dt,float(-.025) * dt, 0 }); gameobjects[0]->nextmapcoords.x = gameobjects[0]->mapcoords.x + 1; gameobjects[0]->nextmapcoords.y = gameobjects[0]->mapcoords.y - 1; } } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::S) && sf::Keyboard::isKeyPressed(sf::Keyboard::A)) { if (gameobjects[0]->getPhysics()->inMotion == false) { gameobjects[0]->setVelocity(XMFLOAT3{ float(-.025) * dt,float(-.025) * dt, 0 }); gameobjects[0]->nextmapcoords.x = gameobjects[0]->mapcoords.x - 1; gameobjects[0]->nextmapcoords.y = gameobjects[0]->mapcoords.y - 1; } } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) { if (gameobjects[0]->getPhysics()->inMotion == false) { gameobjects[0]->getPhysics()->SetVelocityX(float(.025) * dt); gameobjects[0]->nextmapcoords.x = gameobjects[0]->mapcoords.x + 1; gameobjects[0]->nextmapcoords.y = gameobjects[0]->mapcoords.y; } } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) { if (gameobjects[0]->getPhysics()->inMotion == false) { gameobjects[0]->getPhysics()->SetVelocityX(float(-.025) * dt); gameobjects[0]->nextmapcoords.x = gameobjects[0]->mapcoords.x - 1; gameobjects[0]->nextmapcoords.y = gameobjects[0]->mapcoords.y; } } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)) { if (gameobjects[0]->getPhysics()->inMotion == false) { gameobjects[0]->getPhysics()->SetVelocityY(float(.025) * dt); gameobjects[0]->nextmapcoords.x = gameobjects[0]->mapcoords.x; gameobjects[0]->nextmapcoords.y = gameobjects[0]->mapcoords.y + 1; } } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) { if (gameobjects[0]->getPhysics()->inMotion == false) { gameobjects[0]->getPhysics()->SetVelocityY(float(-.025) * dt); gameobjects[0]->nextmapcoords.x = gameobjects[0]->mapcoords.x; gameobjects[0]->nextmapcoords.y = gameobjects[0]->mapcoords.y - 1; } } So, I had to add some information to my gameobject class. Specifically, map coordinates, and next map coordinates. Then I had to limit my input to only when inmotion is false, which will be false whenever I have successfully locked to a tile. Now, to make all the map coordinates work correctly, I needed to be able to update the next map coordinates and current map coordinates in my game object update loop, using this code: void Game::UpdateMapPosition(GameObject * object) { XMFLOAT3A objectpos = object->getPosition(); int xcount = floor(objectpos.x / map->tileSizeX); int ycount = floor(objectpos.y / map->tileSizeY); object->mapcoords.x = xcount; object->mapcoords.y = ycount; XMFLOAT3A pos = map->At(xcount, ycount); if (object->currentDirection == Facing::UP_RIGHT || object->currentDirection == Facing::DOWN_LEFT) { if (pos.x == map->At(object->nextmapcoords.x, object->nextmapcoords.y).x) { object->getPhysics()->SetVelocityX(0); } else if (pos.y == map->At(object->nextmapcoords.x, object->nextmapcoords.y).y) { object->getPhysics()->SetVelocityY(0); } } else if (object->currentDirection == Facing::UP_LEFT || object->currentDirection == Facing::DOWN_RIGHT) { if (pos.x == map->At(object->nextmapcoords.x, object->nextmapcoords.y).x) { object->getPhysics()->SetVelocityX(0); } else if (pos.y == map->At(object->nextmapcoords.x, object->nextmapcoords.y).y) { object->getPhysics()->SetVelocityY(0); } } else if(object->currentDirection == Facing::RIGHT || object->currentDirection == Facing::LEFT) { if (pos.x == map->At(object->nextmapcoords.x, object->nextmapcoords.y).x) { object->getPhysics()->SetVelocityX(0); } else if (pos.y == map->At(object->nextmapcoords.x, object->nextmapcoords.y).y) { object->getPhysics()->SetVelocityY(0); } } else if(object->currentDirection == Facing::UP || object->currentDirection == Facing::DOWN) { if (pos.y == map->At(object->nextmapcoords.x, object->nextmapcoords.y).y) { object->getPhysics()->SetVelocityY(0); } else if (pos.y == map->At(object->nextmapcoords.x, object->nextmapcoords.y).y) { object->getPhysics()->SetVelocityY(0); } } } Again, definitely open to suggestions for cleanup here, this is what I was able to throw together given my current knowledge and understanding. The end result is definitely functional though! The next thing I think I want to do is incorporate the mouse in some kind of way, but I haven't thought up any ideas yet, lets here what you think! There are a few things I like about this setup, and of course a few that I don't like. I think the code is pretty easy to read, and I like that. However, It feels a bit bloated and large for the job its actually accomplishing, definitely some room for some tidying up and optimization.   Anyways I think I've made a good start for the next challenge. What's next? Maybe make some of my objects interactable, like boxes and crates and junk. Loot is, I believe, the biggest part of the requirement, aside from beating the bad guys!

## Final Submission for the Challenge!

WOOOOO, last 3 days have been epic. I was able to add sound, a menu, a game over screen, and a score in 3 nights.       Here it is! Enjoy! Update: Gogger.zip - Some bug fixes... already!

## Day 2 Feature 2 of 4 - Music

I was able to use SFML to bring in my recording of my banjo. Seems to suit the goblin nicely. Unfortunately, the video recording is paying the price of the addition of sound (recording framerate is choppy, not quite sure how to fix). Everything works smoothly when it plays though, and here is the result. Gogger loves the Banjo. In order to accomplish this, only a very small amount of code is needed, more work was involved in importing the libraries. sf::Music music; if (!music.openFromFile("../MyGame/Sound/Recording1.ogg")) return -1; // error music.play(); Here is a URL if the video doesn't load! https://puu.sh/C9WZ1/29e00140bf.mp4 Gogger Music!.mp4

## 3 Days, 4 features - Menu

I've come to realize that I will not make the deadline unless I seriously crunch, so, enter game menu, witness the monstrosity - dies a little on the inside. Everything is super basic, simple Boolean if else to determine if I'm in the menu, some conditions on the input to detect if I'm in the menu so I can handle it appropriately, and a silly png I made in gimp to represent the menu. This hardly counts as a blog entry, but join me in the exciting dash to delivery day, (2 more days). 2018-11-28_23-59-22.mp4

## New Map and Water Trap!

Wow, this last couple weeks has been quite a journey. As soon as I updated my map and added a water feature obstacle, I realized I had no obvious way built into my code to make it behave like it should for a frogger-esque type game. The water had to kill me, but, not if I was standing on a platform. This led to the addition of some features in the engine that I hadn't thought of initially. Namely, a collision mask. What I decided to do was make it so if I was colliding with water, it would decrease the Z axis up until a certain point. Once that point is met on the Z axis, then call the game logic to do its thing. It looks like this: else if (collidedObjectB->getCollisionType() == CollisionResponseType::SINK) { if (collidedObjectA->getPosition().z >= -1) { collidedObjectA->getPosition().z -= .1f; } else { if (collidedObjectA->userData != nullptr && collidedObjectB->userData != nullptr) gameCollisionResponse(*collidedObjectA, *collidedObjectB); } }   So, naturally, the physical object connected to water has the "SINK" collision response, which causes whatever is colliding with it to sink at a rate of -.1f per frame. If we find that we exceed the threshold for Z we call the game logic response gameCollisionResponse: // if the player runs into water else if (objectA->Type == GameObjectType::PLAYER && objectB->Type == GameObjectType::WATER) { objectA->resetPosition(); } This response is gated so that it only works on the player, after posting this code I realize my platforms could be sinking in Z perpetually, but this can't be perceived in the rendering (adds to fix list).

Now, all this works great for killing the player, but now I needed a way to make it so if the player was standing on a platform, it nullified the sinking effect. So, I set up a new game object called platform and gave its physical component a "FLOAT" mask, enter logic: else if (collidedObjectA->getCollisionType() == CollisionResponseType::STOP && collidedObjectB->getCollisionType() == CollisionResponseType::FLOAT) { collidedObjectA->getPosition().z = 0; } Now, this will reset the Z of the player so that he doesn't reach the threshold we talked about earlier. I'm not certain that I need the other condition here (if objectA = type "STOP"), I'll mess with that sometime.   As you can probably imagine, the changes that were required in order to get this working properly were a lot further reaching than just this obvious code, but the end result is looking great! Also, I'd like to mention that I've updated the map with new sprites! It's looking pretty good now I think. No new enemies yet, I need to figure out how to pack the separated sprites into atlases so I can animate them properly.  Just a few more things to do, need to have a victory trigger, and then some kind of menu! Oh, and a score system, ugh... Definitely a bit more work to do! I'm not certain if I will make the deadline, but participation has been a blast so far!   2018-11-24_12-36-11.mp4

## Game Events based on Physics Interactions

So, last night I jumped a giant hurdle in design with regards to triggering game events. The problem I faced was that I had two systems that were decoupled, Game and Physics, however, the goal was to generate a game event from a physics interaction, in this case, an AABB collision. This required the use of a callback function, which I determined logic for in the Game Class, but registering it with the physics class, so that the physics could use the callback to initiate game events. In my case, I wanted to make an explosion, so here we have this bit:   this->onIntersect = [this](PhysicalObject & a, PhysicalObject & b) { GameObject * objectA = reinterpret_cast<GameObject*>(a.userData); GameObject * objectB = reinterpret_cast<GameObject*>(b.userData); if (objectA->Type == GameObjectType::ENEMY && objectB->Type == GameObjectType::PLAYER) { GenerateExplosion(objectA->getPosition(), 64, 64); objectB->resetPosition(); } };   The call back takes two physical objects that have collided and reinterprets their void userData pointer into a game object. Then, after determining what game object types we're dealing with, we apply the logic, in this case, we want to generate an explosion, and for now, I'm resetting the goblin's position to its origin. The GameObjectType is something I just newly added to this process, and it has also enabled me to gate the response of the trigger objects, so that the player is not reset back to its origin if it runs into a lane trigger box (the thing that causes my lanes to reset).   Another problem was identified upon successfully generating an explosion, it didn't animate! This initiated a long string of changes to how my game objects handle their animations. My game object was previously reliant on whether or not the object was in motion (a boolean provided by the physical component) to determine whether it should animate or not. Therefore, no movement, no animation. This caused me to restructure how I handle animations. Now, I have set it up so that doing nothing is actually running an IDLE animation, even if that animation is just one frame. Now, things are always animating. There were a lot of ins and outs to this change but I'll post the updated game object update code so that you can get an idea: void GameObject::Update(float dt) { Position = myPhysics->getNewPosition(dt); if (myPhysics->getDirection() >= 0) { currentDirection = myPhysics->getDirection(); } mySprite->setPosition(Position); mySprite->SetAnimation(currentAction, currentDirection); if (updateTimer.GetMilisecondsElapsed() >= mySprite->getFrameTime()) { mySprite->AdvanceFrame(); updateTimer.Restart(); } }   Changes summarized: Game object needed to be aware of its current facing direction Set animation needed modified to take an action, and a direction, this was quite a large undertaking, taking around 3 hours for me to get working. I had to change the way I initialized my animations, as well as use the currentAction and currentDirection to get a key into the animations hash map. Remove dependency of bool inMotion to determine whether or not to animate Add idle animations to everything, and a default animation set as IDLE/UP if there is only one animation Cause all game objects to start in facing position UP etc... the list goes on Here is the result! Game Event Triggers and Updated Animations!.mp4 This has paved the way for me to meet some of the other requirements of the challenge, namely, triggering game win and game over events. Also, I just purchased a humblebundle pack, here: HumbleBundle RPG Pack So, hopefully, next time I post, we'll have some vastly improved visuals and new baddies to play with! But, not before I set up some of the aforementioned triggers, not to mention the obvious, making it so my generated explosions don't perpetually exist :).

Oh, and the explosion is from here: https://mbtskoudsalg.com/explore/explosion-sprite-sheet-png/#gal_post_3691_explosion-sprite-sheet-png-3.png, after reading the site terms I decided it was safe to use.

## Game Initialization

I've spent the night shrinking the amount of code it takes to set up my map, goblin player, enemies, and triggers. Here is what I've come up with:   void Game::Initialize(HWND hWnd) { // initialize engine components! renderer = new Renderer(hWnd); physics = new Physics(); cam = renderer->getCamera(); CSVReader reader; map = new Map(20, 100, TILESIZE_X, TILESIZE_Y); map->BuildMapFromFile(renderer, physics, reader.getData("../MyGame/MapFiles/gogger_Base.csv"), 1, FALSE); map->BuildMapFromFile(renderer, physics, reader.getData("../MyGame/MapFiles/gogger_Foliage.csv"), 2, TRUE); // set up game objects Game_Object * goblin = new Game_Object(renderer->RegisterSprite(SpriteID::GOBLIN, map->At(97, 10), 64, 64, 2), physics->RegisterPhysicsObject(32, 32, map->At(97, 10))); Game_Object * skeleton = new Game_Object(renderer->RegisterSprite(SpriteID::SKELETON, map->At(93, 19), 64, 64, 2), physics->RegisterPhysicsObject(32, 32, map->At(93, 19))); Game_Object * skeleton1 = new Game_Object(renderer->RegisterSprite(SpriteID::SKELETON, map->At(92, 0), 64, 64, 2), physics->RegisterPhysicsObject(32, 32, map->At(92, 0))); // define behavior for game objects skeleton->setVelocity(XMFLOAT3{ -.25,0,0 }); skeleton->setAction("WALK_LEFT"); skeleton1->setVelocity(XMFLOAT3{ +.25,0,0 }); skeleton1->setAction("WALK_RIGHT"); // store game objects Game_Objects.push_back(goblin); Game_Objects.push_back(skeleton); Game_Objects.push_back(skeleton1); //set up trigger boxes for obstacle lanes LaneTriggers.push_back(physics->RegisterTriggerBox(map->getDesc(93, 0))); LaneTriggers.push_back(physics->RegisterTriggerBox(map->getDesc(92, 19))); // set lane trigger response for (int i = 0; i < LaneTriggers.size(); i++) { LaneTriggers[i]->onIntersect = [](PhysicalObject & object) { object.resetPosition(); }; } // start the clock! updateTimer.Start(); }   This is enough code to set up my first lane, adding more lanes is just adding more objects, and creating triggers for each lane. I could build some Gogger specific objects that define the lanes, as far as direction , start position, and maybe even quantity of game objects. This could consolidate a lot of code and tuck it nicely away. However, I have to attend to some more pressing issues, which are actual requirements for the challenge.    Game Over (You Lose) This occurs when Gogger the Goblin collides with an enemy or a maptile that is a trap (need a way to define map tile collisions that result in death) Currently my physical objects don't know what they're running into. I think I need to set up a bit mask for physical objects... This way, the physics engine can better handle collisions, if the collided objects are player and enemy, make the player game object dead I think I need to use a trigger box for the deadly map tiles. I can maybe use Tile2d map editor to define their bounds, I can check the kill object trigger(s) to see if its triggered by a player, and set the trigger box behavior to make the game object dead Game Over (You Win) For this I can probably use a trigger box as well, only defining the behavior a little differently Score Haven't even given this any thought yet.

• Advertisement
• Advertisement
• ### Blog Entries

• Advertisement
×

## Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!