Jump to content
  • Advertisement

trill41

Member
  • Content Count

    31
  • Joined

  • Last visited

Community Reputation

131 Neutral

About trill41

  • Rank
    Member

Personal Information

  • Role
    Programmer
  • Interests
    Programming

Social

  • Github
    stievie

Recent Profile Visitors

3202 profile views
  1. If you added large binary files (e.g. game assets like models, textures, sounds etc.) to your repository, all VCSs (like git, Mercurial, CVS, Subversion) will become slow, because they were made for source code, i.e. text files. These systems normally just store the difference to the previous state, which keeps the repositories small. But this only works with text files. When you change a binary file the whole file is stored, which bloats the repository and makes it slow. I didn't solve this problem yet, I just don't add binary assets to the source repository at all. I have a Seafile instance running, which synchronizes my assets folder with a server.
  2. trill41

    Event system

    When I realized that the classes are getting really big and complicated, I splitted them into smaller classes. So the Actor class doesn't do everything anymore, but has some components that do the work instead. A Common problem with this approach is, how do Compositors (in this case the Actor) and Components communicate? Some might say they shouldn't communicate at all, use something like an ECS etc. I looked at ECS et al., and they may be fine when you design your application around it, but I didn't. Up to now I just friend'ed everything and called private methods, but that doesn't seem to be very elegant. Another approach is some event/messaging system. So interested parties subscribe to interesting events, and other may trigger those events. There are many such event systems out there, but all seemed to be too heavy for me, so I made a minimal implementation of such a system. I think this implementation has some Pro's and many Con's: Pros Type safety Event function can have any signature even with return value. The `CallOne()` and `CallAll()` functions returns the same type as the called function (or a vector of it). You must define at compile time the signatures of functions your a going to call, and you can not call anything else. You get a compile error when a function signature is not found. A called function may be a `std::function` or Lambda. No inheritance needed. Single header file `events.h`, only ~110 lines. Minimal run time overhead. Cons Function signature must be passed to each call of `Subscribe()`, `CallOne()` and `CallAll()`. You must define at compile time the signatures of functions your a going to call If you do anything wrong (e.g. wrong function signature), you get weird error messages Increased compile time, because a lot is done at compile time. But what you can do at compile time, doesn't have to be done at run time. You can not unsubscribe from events :(. Examples Lambda sa::Events< int(int, int) > events; events.Subscribe<int(int, int)>(1, [](int i, int j) -> int { return i * j; }); auto result = events.CallOne<int(int, int)>(1, 2, 3); static_assert(std::is_same<decltype(result), int>::value); assert(result == 6); Lambda 2 sa::Events< int(int, int) > events; auto func = [](int i, int j) -> int { return i + j; }; events.Subscribe<int(int, int)>(2, func); auto result = events.CallOne<int(int, int)>(2, 4, 5); static_assert(std::is_same<decltype(result), int>::value); assert(result == 9); Event not found When an event does not exist or nobody subscribed to the event it returns a default value, e.g. `0` for an `int`: sa::Events< int(int, int) > events; events.Subscribe<int(int, int)>(1, [](int i, int j) -> int { return i * j; }); auto result = events.CallOne<int(int, int)>(2, 2, 3); static_assert(std::is_same<decltype(result), int>::value); assert(result == 0); Methods class Foo { private: sa::Events< int(int, int) > events; int Bar(int i, int j) { return i * j; } public: Foo() { events.Subscribe<int(int, int)>(1, std::bind(&Foo::Bar, this, std::placeholders::_1, std::placeholders::_2)); } int DoBar(int i, int j) { return events.CallOne<int(int, int)>(1, i, j); } }; Foo foo; auto result = foo.DoBar(3, 2); assert(result == 6); Different signatures sa::Events< int(int, int), bool(int), void(void) > events; events.Subscribe<int(int, int)>(1, [](int i, int j) -> int { return i * j; }); events.Subscribe<bool(int)>(2, [](int i) -> bool { return i != 0; }); events.Subscribe<void(void)>(3, []() { std::count << "No arguments :(" << std::endl; }); auto result = events.CallOne<int(int, int)>(1, 4, 5); static_assert(std::is_same<decltype(result), int>::value); assert(result == 20); auto result2 = events.CallOne<bool(int)>(2, 5); static_assert(std::is_same<decltype(result2), bool>::value); assert(result2 == true); // void events.CallOne<void(void)>(3); Multiple subscribers sa::Events< void(const std::string&) > events; events.Subscribe<void(const std::string&)>(1, [](const std::string& s) { std::cout << "Subscriber 1 " << s << std::endl; }); events.Subscribe<void(const std::string&)>(1, [](const std::string& s) { std::cout << "Subscriber 2 " << s << std::endl; }); events.CallAll<void(const std::string&)>(1, "Hello Subscribers!"); Should print: Subscriber 1 Hello Subscribers! Subscriber 2 Hello Subscribers! Conclusion I was able to get rid of many virtual functions and the classes got lighter. But the usage is a bit cumbersome, because you always have to pass the function signature of the event as template argument, but you get type safety in return. Download Get it from the Github respository if you are interested (MIT).
  3. trill41

    ABx

    Online role playing game in an ancient Greece setting at the time of the Trojan war. I guess this a technical project, not so much an art project, that's why there is a screenshot from the server, not the client 🤣. License The source code is licensed under the MIT License and can be downloaded from the Github repository. The assets are a different story, I'm not going to upload/check in some GBs of binary data. Features so far Uses a single TCP stream for the game protocol. This is not a shooter or action game, more like a classical online RPG. Database back-end is currently PostreSQL. Instanced world. Encrypted Game and Login Protocol with DH key exchange. Entity interpolation, client prediction. Spawn any number of server which may have heavy load (game, file server) even on different hardware. Chat: Local (map), Guild, Party and Whisper chat, even across different game server. Client and server use the same scene file. Navigation using Recast/Detour. Server and Client runs on Windows and Linux. However, the Client has some bugs on Linux. Server can be compiled as NT service which also takes care of the startup order. On Linux they can run as Daemons under systemd. Optional Browser Admin interface to control servers, view/edit the database. https://devtube.dev-wiki.de/video-channels/trill_channel/videos
  4. trill41

    Tightly packed float/double ?

    I was not able to reproduce that. My code (copy of yours): vec.h #pragma once template <typename T> struct vec4_t { explicit vec4_t<T>( T const &s1, T const &s2, T const &s3, T const &s4 ) : x{s1}, y{s2}, z{s3}, w{s4} {} union { struct{ T x, y, z, w; }; struct{ T r, g, b, a; }; struct{ T s, t, u, v; }; }; }; mat.h #pragma once #include "vec.h" template<typename T> struct mat4_t { explicit mat4_t<T>( T const& x ) : value{ vec4_t<T>{ x, 0, 0, 0 }, vec4_t<T>{ 0, x, 0, 0 }, vec4_t<T>{ 0, 0, x, 0 }, vec4_t<T>{ 0, 0, 0, x } } {} vec4_t<T> value[4]; }; main.cpp int main() { mat4_t<float> mat{1.0f}; std::cout << sizeof (mat) << std::endl; auto* data = &(mat.value[0].x); for( int i{0}; i < 16; ++i ) std::cout << data[i] << ' '; std::cout << std::endl; } Result: 64 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 Compiled with GCC 9.1 64 Bit Linux.
  5. trill41

    Friend Foe identification

    You are right, the wording was a bit bad. I can imagine situations where a list maybe the way to go. I did not benchmark any of those, but I imagine some bit operations should be faster than looking up an item in a list (if lookup is not O(1)). Another point was, I'm a "lazy typer", and this are just some lines of code. And I didn't want to mess up the Actor class to much, since it's already complicated. I wouldn't say invalid, nonsense, yes, but that could also lead to funny results 😁. But even if you have 2 lists for friends and foes, it has the same problem. Groups can be in both of them, if you don't take care. However, I agree that my method may be more error prone. I was looking for solutions how other games solved this problem. I think this must be a common problem in games, but I didn't find much (maybe I didn't look hard enough). So I thought I throw it in and see what others think. Thank you for the comment.
  6. trill41

    Friend Foe identification

    The game mechanics doesn't allow to use damage skills on allies and heal or protection skills on foes. This makes it necessary that the game can identify allies and foes. A simple if statement seems to be sufficient for that, but it's not. There can be many different combinations of relations between the groups in a game, see the image. So there are different groups which have different relations to each other. So how to solve this problem? One could have the idea to maintain a list of friend and foe groups, but well, not really, because that's probably the most inefficient way to do this. Instead I chose to use a Bit mask which encodes the friends as well as the foes. The Actor class was extended as shown bellow: class Actor { private: /// Lower 16 bit friends, upper 16 bit foes /// This gives us 3 types of relation: (1) friend, (2) foe and (3) neutral /// and (4) in theory, both but that would be silly. unsigned groupMask_{ 0 }; /// Get lower 16 bits of the group mask unsigned GetFriendMask() const { return groupMask_ & 0xffff; } /// Get upper 16 bits of the group mask unsigned GetFoeMask() const { return groupMask_ >> 16; } public: void AddFriendFoe(unsigned frnd, unsigned foe) { groupMask_ |= (frnd | (foe << 16)); } void RemoveFriendFoe(uint32_t frnd, uint32_t foe) { groupMask_ &= ~(frnd | (foe << 16)); } bool IsAlly(const Actor* other) const { // Return true if they have matching bits in the friend mask return ((GetFriendMask() & other->GetFriendMask()) != 0); } bool IsEnemy(const Actor* other) const { // Return true if we have a matching bit of our foe mask in their friend mask return ((GetFoeMask() & other->GetFriendMask()) != 0); } }; This makes it possible to have 16 different groups on one map, which have different relations to each other. To define who is friend with whom and who is an enemy of whom, only the AddFriendFoe() method has to be called when the Actor is created: void CreateActorsForGame() { static const unsigned GROUPMASK_NONE = 0; static const unsigned GROUPMASK_1 = 1; static const unsigned GROUPMASK_2 = 1 << 1; static const unsigned GROUPMASK_3 = 1 << 2; static const unsigned GROUPMASK_4 = 1 << 3; static const unsigned GROUPMASK_5 = 1 << 4; static const unsigned GROUPMASK_6 = 1 << 5; static const unsigned GROUPMASK_7 = 1 << 7; static const unsigned GROUPMASK_8 = 1 << 8; // Can have up to 8 more static const unsigned GROUPMASK_ALL = 65536; Actor trojan1; trojan1.AddFriendFoe(GROUPMASK_1, GROUPMASK_2 | GROUPMASK_3); Actor trojan2; trojan2.AddFriendFoe(GROUPMASK_1, GROUPMASK_2 | GROUPMASK_3); Actor pacifist; pacifist.AddFriendFoe(0, 0); Actor roman; roman.AddFriendFoe(GROUPMASK_2, GROUPMASK_1 | GROUPMASK_3); Actor player; player.AddFriendFoe(GROUPMASK_3, GROUPMASK_1 | GROUPMASK_2); }
  7. trill41

    ABx

    Album for ABx
  8. trill41

    Projectiles

    Projectiles Unfortunately Bows and Spears already existed in ancient Greece, so I had to implement something that feels like a projectile. Projectiles are fast moving Actors heading towards a target. If they hit the target it damages the target. Like in other online RPGs a projectile can not hit the wrong target, it either hits the desired target or it misses it. Projectiles have some special problems. Problems They are fast moving, they fly, the target can be obstructed. Fast moving The game may not check if the projectile collides with the target, because it runs only 20 game updates per second, and that is by far too low. The projectile will just fly through the target and the game won't even notice. So it must approximate if the projectile would hit the target. For now I solved this as follows. If the distance to the target is increasing, check if it's lower than a threshold, which also considers the moving speed. Also projectiles may move with a different speed. Flying All actors stick to the ground, except projectiles, so they need some special treatment. Unlike other actors, projectiles also move along the Y axis. Obstacles If the target is behind an obstacle, the projectile can not hit it. This applies also for the terrain, e.g. when the target hides behind a hill. The Terrain is not a regular game object and is not considered in a Raycast query. But there are Terrain patches, which are regular game objects, and therefore are in an octand of the octree, so a Raycast query will return Terrain patches. In the picture bellow we can see that Bob can shoot at Alice, but he will not hit her, because Terrain Patch 2 in in the way. Unlike other game objects, a Terrain patch can not use its bounding box, because it would be too big, see bellow. Terrain patches must use the height values of the terrain to check if the ray collides. Game mechanic considerations If you ever threw an ashtray after a fly you know that hitting a moving target is really hard. So the player will need some assistance from the game to make it easier, or nobody would ever use projectiles. In this game a projectile will always hit the target, even when the target is moving. A target can dodge when the target is changing the move direction after the projectile started. Area of Effect As the name implies, this is a place which has an effect on the actors inside. The screenshot bellow shows such an area of effect, it's some toxic mushrooms that damages and poisons actors in it. Other use cases are Traps, effects that are spawend by skills, e.g. skills that make damage in a certain range over time, etc.
  9. trill41

    Running on real Server Hardware

    Some days ago I got old server hardware. It's a Xeon with 4 cores @ 2.40GHz with 8GB RAM an SSD and 2 HDDs in RAID1. I installed Debian 10 Buster with Xen to run it as a Bare Metal Hypervisor, just like a real server. To install and deal with Xen - I never used it before, and I'm developer not administrator -, I found this article very helpful. Creating a new VM Once Debian and the Xen System is installed and you have booted Xen and logged into the Control Domain (Domain-0), using it is a "nobrainer", but first of all you may want switch to the root user (su - root). Creating a new VM is just one command line, like: xen-create-image --hostname=buildserver --lvm=vg0 --dist=buster --size=8G --memory=2048M --maxmem=2048M --swap=2048M --pygrub --dhcp So you just call xen-create-image with a hostname, storage size, how much memory it should get and what distribution (it doesn't have to be Debian, Xen supports many different distributions out of the box). This will create the volumes (disk and swap), download and install the distribution, and it will create the configuration file. Bellow you can see the volumes for my VMs: To start the VM it is enough to call xl create <config-file>, to stop it xl destroy <name>. Building for Debian The servers build on Manjaro with Clang since some time now, but since the VMs are all Debian they must also build on/for Debian. For this I created a Debian VM with all required development tools and libraries, just to compile the servers for Debian. The differences is, that with Manjaro you get bleeding edge software, while software on Debian often lag behind a bit. I'm still not smart enough for CMake, so I created makefiles for GNU make which should be quite generic so they don't need much attention, e.g. there is no need to manually add or remove files. The goal is to just do a git pull && cd debian && make and it should compile the latest version. There were some minor issues I had to fix, but now the servers build with Clang 8.0.0, Clang 7.0.1 and GCC 8.3.0 fine on Manjaro and Debian. Running the new VMs Running stuff for the first time is always exciting, but everything went fine, I just had to install some Libraries (DB client libraries on the data server etc.). Bellow you can see the running VMs: The Data server inside it's own VM: All other servers inside the abgame VM: Connecting to the new Server The first observation was that I got back my nice 50ms Ping (round trip time) 😀. You may wonder why the round trip time is always at least 50ms. Earlier I mentioned the server runs 20 updates per seconds, this means it runs one update every 50ms. That's why the round trip time is at least 50ms.
  10. trill41

    Linux and Client prediction

    Linux Some time ago I started to port all the servers to Linux. This process is more or less finished now, it compiles fine with Clang (didn't try GCC) and it even runs without any problems. For that I also had to port DirectXMath, which is used by the game server, to Linux. Even the client compiles and runs on Linux, but it doesn't work well, it always loses the connection to the game server. Now that everything compiles and runs, I tried to connect with the client to the server running inside a Linux VM. What I realized first was that I'm dealing now with real world conditions when it comes to network latency. When the client connects to a server running on the same machine, I always had a Ping (actually a round trip) of 50ms, which is fine. When the client is connected to the Linux server, I get a round trip of ~200ms - ouch! To be honest, I have no idea why it is so high, because the server and client still runs on the same hardware. Anyway, I think an online RPG must deal with pings above 200ms, and even much higher pings. Client prediction In the screenshot above, the round trip time (RTT) is ~250ms, this means it takes 250ms from sending the move command to get the new position. 250ms doesn't sound much, but it is perceivable and feels really bad. To deal with that problem, there are different techniques, one is Client prediction, what I made now. Client prediction means, the players client calculates its position on its own and moves the player to this likely position. It does not wait to get a position from the server. From time to time, the client checks if the client position and the server position are too different and adjusts the client position to the server position, also known as rubberbanding. This causes several problems: The position of the player on the client and server are probably not the same. Rubberbanding Sometimes the players character is controlled by the server, e.g. when following an other actor. This is easy to fix, the server just sends a message to the player to turn off client prediction. The players client is always ahead of the server and all other clients, but that might not be a big problem, since it's just visual. The client is very dump and does just a rough estimation of the likely position, without considering collisions resulting in even worse rubberbanding. While the first are mere details, the collision checking seems to be a tough one. For now I have just added a message that forces the client to set the position of an object, e.g. when it would collide with an object. This results in even more jittering, but I look forward to implement basic collision checking also on the client.
  11. trill41

    Items and AI

    Items I was thinking about weapons and equipment's, which all are Items. So sooner or later some item system must be implemented. All items are stored in a database table (let's name it game_items) with a name, value, the type of the item (weapon, armor, crap...), a 3D model file (which is loaded by the client) and an optional Lua script file, because e.g. weapons are scriptable. In OOP terms the game_items table has a list of item classes. Now we just need another table with the objects, because when an item drops for a player and the player picks it up, the player doesn't want that this item disappears, so it must be stored in the database. Let's name this table concrete_items. This table contains what item it is (so the primary key of the item in the game_items table), where it is stored, to which player it belongs, item stats (like damage for weapons), and some other information like creation time, value etc. Drops When NPCs die, they may or may not drop a random item. Each item has a certain chance to drop on a certain map. This requires another database table which associates an item with a map and a chance to drop on this map. Selecting a random item from a pool of items that may drop on the current map isn't really hard, there are algorithms that do that, like WalkerMethod which is O(1), or my adaption of it. Items drop for certain players, other players can not pickup an item dropped for a different player. When an NPC dies and it drops an item, an item is picked from the item pool and a new concrete item is created and inserted into the database with random stats. A random player is selected from the party that killed the NPC and this new item drops for this player. If a player picks up an item, it is added the the players inventory. Technically just the storage place of the concrete item is changed from Scene to Inventory. AI First I thought I'll make a PvP only game because then I don't need to mess around with game AI, but I realized even PvP games need some NPCs that have some kind of AI. Then I came across a behavior tree library (SimpleAI) that seemed to be a nice fit. It was really easy to integrate this library into the game server. ai::Zone A Game instance has a Map object with the terrain, navigation mesh, the octree and now an ai::Zone object. ai::ICharacter and ai::AI The class hierarchy of game objects looks a bit like this. SimpleAI controls the NPC via the ai::ICharacter class. The NPC class could just inherit also from the ai::ICharacter class, but I try to avoid multiple inheritance where I can, so the NPC class has a ai::ICharacter member. Then the NPC has an ai::AI object which has the behavior and does all the "intelligent" stuff. Behaviors Behaviors are defined with simple Lua scripts, e.g.: -- Try to stay alive function stayAlive(parentnode) -- Executes all the connected children in the order they were added (no matter what -- the TreeNodeStatus of the previous child was). local parallel = parentnode:addNode("Parallel", "stayalive") parallel:setCondition("IsSelfHealthLow") parallel:addNode("HealSelf", "healself") -- TODO: Flee end -- Heal an ally function healAlly(parentnode) local parallel = parentnode:addNode("Parallel", "healally") parallel:setCondition("And(IsAllyHealthLow,Filter(SelectLowHealth))") parallel:addNode("HealOther", "healother") end -- Do nothing function idle(parentnode) -- This node tries to execute all the attached children until one succeeds. This composite only -- fails if all children failed, too. local prio = parentnode:addNode("PrioritySelector", "idle") prio:addNode("Idle{1000}", "idle1000") end function initPriest() local name = "PRIEST" local rootNode = AI.createTree(name):createRoot("PrioritySelector", name) stayAlive(rootNode) healAlly(rootNode) -- ... idle(rootNode) end So now we have a behavior with the name "PRIEST" and all NPCs with this behavior try to stay alive, heal an ally or do nothing. Conditions, Filters and Actions Of course the IsSelfHealthLow is not part of SimpleAI (although it already comes with a set of Conditions, Filters and Actions). The IsSelfHealthLow condition just checks if the health points of the NPC is under some threshold: class IsSelfHealthLow : public ai::ICondition { public: CONDITION_CLASS(IsSelfHealthLow) CONDITION_FACTORY(IsSelfHealthLow) bool evaluate(const ai::AIPtr& entity) override { const ai::Zone* zone = entity->getZone(); if (zone == nullptr) return false; const AiCharacter& chr = entity->getCharacterCast<AiCharacter>(); const auto& npc = chr.GetNpc(); if (npc.IsDead()) // Too late return false; return npc.resourceComp_.GetHealthRatio() < LOW_HP_THRESHOLD; } }; If now IsSelfHealthLow::evaluate() evaluated to true it executes the action HealSelf which is again a C++ class. It tries to find a skill that do some self healing, and uses it. For just staying alive, no Filters are needed, but for a Priest that may also heal others, Filters are need to select those Allies with low health points. Such a Filter class could look like this: void SelectLowHealth::filter(const ai::AIPtr& entity) { ai::FilteredEntities& entities = getFilteredEntities(entity); Game::Npc& chr = getNpc(entity); std::map<uint32_t, std::pair<float, float>> sorting; chr.VisitAlliesInRange(Game::Ranges::Aggro, [&](const Game::Actor* o) { if (o->resourceComp_.GetHealthRatio() < LOW_HP_THRESHOLD) { entities.push_back(o->id_); sorting[o->id_] = std::make_pair<float, float>(o->resourceComp_.GetHealthRatio(), o->GetDistance(&chr)); } }); std::sort(entities.begin(), entities.end(), [&sorting](int i, int j) { const std::pair<float, float>& p1 = sorting[i]; const std::pair<float, float>& p2 = sorting[j]; if (fabs(p1.first - p2.first) < 0.05) // If same HP (max 5% difference) use shorter distance return p1.second < p2.second; return p1.first < p2.first; }); } Now the HealOther class can get the filtered entities (which is just a std::vector<uint32_t> containing the IDs of the allies with low health points sorted by priority), and use some heal skill on that target. Conclusion SimpleAI is a great library, it is easy to integrate, easy to use and easy to configure, and as far as I can see now, it just works.
  12. trill41

    Gameplay

    For the last two years I worked almost only on the foundation of this project, e.g. networking, how servers communicate, database back end, different network protocols. Now that this works more or less, I started to work on the gameplay. Party Cooperation is a big thing in this game. You meet in outposts, form a party and enter a game instance with this party and do stuff together, e.g save the world. Implementing this was quite tricky for several reasons: A lot of communication between the server and the clients is needed. Player and Party objects exist only on the game server. The clients party window is complex and needs to handle many events. There are many situations a party must behave differently. Client/Server communication First a player must invite another player. The invited player can accept or decline the invitation. If the player accepts the invitation, it will be added to the party. Of course, party members can leave the party, and party leaders (the first player in the window) has special rights. They can kick party members and invite new players. Entering a game as party As mentioned earlier, when a player enters a game, it disconnects from the game server and reconnects to it telling the server what map it wants to enter. Since party objects only exist on the game server, these are destroyed when the players disconnect. But to be able to reconstruct the party when entering the new game instance, this information must be saved. I decided to (ab)use the data server for that. Mapping The are two distinct map types: Outposts. People can meet there and make a party. Battle areas. People face foes there and fight them. You can map from one outpost to another outpost. Party members can not do that anymore, except the party leader. If other members want to map to another outpost, they must leave the party first. If there is a portal that brings players to a battle area, all members can go through it, and all members of the party will enter the same battle area instance as a party. Once in a battle area, players can not leave the party anymore, but they can map to an outpost. This will cause that these players will leave the party in the battle area. If all players of a party in a battle area type /resign they will be brought back to the last outpost. Also the leader can not kick members anymore. There are probably more edge cases I'm not aware of at the moment. Melee attacks When players are in a battle area, they can fight foes. However, identification friend or foe does not work great yet. At the moment you can attack anyone not in your party. The damage depends on many different things: The weapon that is used by the attacker Any effects that increase damage of the attacker The armor of the attacked Any effects of the target that reduces damage At the moment the damage is always 5, that's because equipment is not implemented yet, so the attacker does not have any weapon (just their fists), and the target does not have an armor. Once the target is dead, the attacker stop attacking. Now that all foes are dead, we can resign. Oh, by the way, I celebrated the 1000th commit some days ago! Next celebration is the 1337th commit 😛. Some other stats (only code I wrote, no third party libraries, STL etc.): ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- C++ 308 6804 2209 47292 C/C++ Header 395 3049 1558 18012 LESS 15 833 7 5069 Lua 74 301 329 1384 XML 12 0 1 1354 HTML 12 90 24 1032 JavaScript 1 28 24 183 DOS Batch 6 9 8 22 Markdown 3 6 0 12 CSS 1 0 7 2 ------------------------------------------------------------------------------- SUM: 827 11120 4167 74362 -------------------------------------------------------------------------------
  13. trill41

    Collisions

    Collisions have been a constant source of frustration, maybe because I decided to use my own Math library, and did not just use Bullet or something. The game does not have full physics simulation, so I thought a physics engine would be overkill, and how hard can it be to write some collision checking code? Well, at least it's not really easy. Collision shapes For now I have the following collision shapes, which can be checked against each other (except Height map vs. Height map does not work): Axis aligned Bounding Box (AABB) Oriented Bounding Box (OBB) Sphere Height map Convex hull All dynamic objects (like Players, NPCs) have an AABB while static scene objects (buildings etc.) have an OBB or Convex hull. AABB As the name suggests, an Axis aligned Bounding Box is parallel to the coordinate axes, regardless of the orientation of the object it wraps. OBB An OBB has the same orientation as the object it wraps. Loading the Scene The scene is made with Urho3Ds editor and saved to an XML file. The Server loads the same XML file with simplified models or just Bounding Boxes. Since the Server does not have something like a Scene Graph, but just a flat array of Game Objects, the loading method differs a bit to the one in Urho3D. Scene Viewer So when running against invisible walls or running through walls, there must be something wrong. It could be that: My Scene loading routine has bugs, the simplified models are wrong, or the collision checking algorithms have bugs. To find that out, I made simple Scene Viewer built into the Server. I realized that only with guessing and trial and error, I will not be able to track it down. Bellow you can see the objects on the Server: There are still problems with different coordinate systems. My Math library was made for DirectX, but the Scene Viewer uses OpenGL with the help of freeglut and glew. One reason for this is, that the Server should be cross platform, at least Windows and Linux, and it's easier to make a simple 3D viewer with OpenGL than with DirectX. Anyway, now I get an impression of what's going on on the Server. To be able to draw the Collision shapes, they must be converted to vertex and index data which form triangles. For example, to generate vertices and indices for a Bounding Box, I use the following: Shape BoundingBox::GetShape() const { Shape s; const Vector3& boundPoint1 = min_; const Vector3& boundPoint2 = max_; const Vector3 boundPoint3 = Vector3(boundPoint1.x_, boundPoint1.y_, boundPoint2.z_); const Vector3 boundPoint4 = Vector3(boundPoint1.x_, boundPoint2.y_, boundPoint1.z_); const Vector3 boundPoint5 = Vector3(boundPoint2.x_, boundPoint1.y_, boundPoint1.z_); const Vector3 boundPoint6 = Vector3(boundPoint1.x_, boundPoint2.y_, boundPoint2.z_); const Vector3 boundPoint7 = Vector3(boundPoint2.x_, boundPoint1.y_, boundPoint2.z_); const Vector3 boundPoint8 = Vector3(boundPoint2.x_, boundPoint2.y_, boundPoint1.z_); // Add the vertices s.vertexData_.push_back(boundPoint1); s.vertexData_.push_back(boundPoint2); s.vertexData_.push_back(boundPoint3); s.vertexData_.push_back(boundPoint4); s.vertexData_.push_back(boundPoint5); s.vertexData_.push_back(boundPoint6); s.vertexData_.push_back(boundPoint7); s.vertexData_.push_back(boundPoint8); s.vertexCount_ = 8; // Create the triangles /* 5------------1 max / | / | 3------------7 | | | | | | 2------------6 | / | / min 0------------4 */ // Front s.AddTriangle(7, 4, 0); s.AddTriangle(0, 3, 7); // Left s.AddTriangle(0, 3, 2); s.AddTriangle(2, 5, 3); // Right s.AddTriangle(4, 7, 1); s.AddTriangle(1, 6, 4); // Up s.AddTriangle(7, 1, 5); s.AddTriangle(5, 3, 7); // Down s.AddTriangle(2, 6, 4); s.AddTriangle(4, 0, 2); // Back s.AddTriangle(6, 2, 5); s.AddTriangle(5, 1, 6); return s; } The same scene as it the Client sees. It turned out to be a combination of all three bugs (of course). Most of them are fixed now, but I still have problems with OBBs. Of course, the Server still can be compiled without the Scene Viewer because it impacts the performance of the server. The Linker will drop then all references to OpenGL too.
  14. trill41

    WinAPI Raw Input confusion

    DirectInput was also for Keyboard and Mouse. Forget what I wrote about XInput, XInput1_3 works on Win7, I must have been confused, sorry.
  15. trill41

    WinAPI Raw Input confusion

    I agree on that. And I also don't see any direction Microsoft is heading to, there was DirectInput then XInput (not running on Win7 without hacks), Window messages etc. All is more or less deprecated. It is the same mess as for UI development for Windows, Win32 API, MFC, WinForms, WPF etc. Again all more or less deprecated, and Microsoft doesn't give any direction. This is really an unfortunate situation.
  • 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!