Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411508 Posts in 69374 Topics- by 58430 Members - Latest Member: Jesse Webb

April 26, 2024, 09:52:07 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityDevLogsRe:creation - action adventure game about undeads
Pages: 1 2 3 [4] 5 6 ... 9
Print
Author Topic: Re:creation - action adventure game about undeads  (Read 27461 times)
eliasdaler
Guest
« Reply #60 on: October 31, 2015, 01:53:19 PM »

Okay, here's what I've been doing today:
Lua enums
As you may know, Lua doesn't have enums. But sometimes I need to pass C++ enum value into Lua function and do something based on it.

Suppose I have this enum in C++

Code: (cpp)
enum class Direction {
    Up,
    Down,
    Left,
    Right
};

Previously I converted enum values to strings (using std::map<Direction, std::string>) and then used it like this in Lua:

Code: (lua)
if(direction == "Up") then
    ... -- do something
end

This was alright for a while, but now I've realized that I can do something a lot better!
Here's what I can do now:

Code: (lua)
if(direction == Direction.Up) then
    ... -- do something
end

Direction is a const global Lua table which can be used similarly to C++ enum. And what's cool is that it has the same values as corresponding C++ enum! So I can do something like this and this will still work:

Code: (cpp)
enum class SomeEnum {
     SomeValue1 = 1,
     SomeValue2 = 8,
     ... // etc
};

I'll tell the implementation details in some blog post later, there's too much to write about. Smiley

Event Manager
Let's talk about events.
Previously I had something callbacks like this:

Code: (cpp)
void SomeClass::onHpChange(Event* e) {
    auto event = virtual_cast<HpEvent*>();
    int hp = event->hp;
    ...
}

So, basically I had a base Event class and lots of events which used it as a base class.
The only thing different about them were arguments.

Why virtual_cast? That's because implementing something like this:

Code: (cpp)
void SomeClass::onHpChange(HpEvent* event) {
    int hp = event->hp;
    ...
}

Is much harder, because functions with different argument types have different types, so they can't be stored in the same array, so you have to use lots of template magic to make this work.
Another problem is that Lua/C++ event communication gets harder, because you have to register all those event classes in Lua or write function which create tables based on the event's data.
I also had pretty bad separation between Lua and C++ callbacks, so I created CppEventManager and LuaEventManager. They had lots of common stuff, so I created a base EventManager template class (I had to store different types of callbacks and listener id's). Stuff was very hard to manage.

So, what did I come up with? First of all, I created EventArg class which can hold some common types (int, float, bool, more about it later). Event has a std::map<std::string, EventArgs> args. (key is a name of the argument)

So, I can create events like this now:
Code: (cpp)
Event e(EventType::HpChangedEvent);
e.setData("hp", 10);
eventManager.queueEvent(e);

and callbacks look like this:

Code: (cpp)
void SomeClass::onHpChange(const Event& e) {
     int hp = e.getData<int>("hp");
     ...
}

EventManager can now easily hold both C++ and Lua callbacks, so communication between Lua and C++ is easy. Lua callback can look like this:

Code: (cpp)
function callback(event) 
    print("HP:" .. event.data.hp)
end

This works, because events can easily be converted to Lua tables with same data.

I have two things I want to discuss though...

1) EventType enum
This is a big enum of all types of events which is used to differ one type from another. Is this okay to use a enum like this or is there a better way? Creating new classes is not an option as I've shown above.
What makes me worry is that compilation times may become slow, because EventManager.h includes EventType.h, so each time I change EventType enum, EventManager.h is recompiled which may cause lots of additional recompilation, which sucks.

2) EventArg
So, what is EventArg? This is a class with union which can hold int, float or bool.
This is enough for most purposes (you may think that std::string is needed, but it isn't, because you most engines don't use std::string's at all and have string integer ids instead.)
But sometimes this is not enough. Sometimes it's good for event to hold sf::Vector2f or sf::FloatRect.
This will probably work with unrestricted unions, but they're not currently supported in VS2013, so I can't use them. (They're supported in VS2015, anyone tried it?)

So, this is how things work now. What do you think about it?
(When I get some time, I'll write more about stuff I've improved in my code in a big blog post :D)
Logged
eliasdaler
Guest
« Reply #61 on: November 22, 2015, 01:25:16 PM »

Progress update
Sorry for a huge delay with a progress update, I've been very busy, mostly finishing the refactoring stuff and making new things!. And now I can say that for the most part it's complete.
Here's how much stuff I've changed last month!

Yep. That's a lot. This includes new stuff I've made this month, so maybe I wrote lots of negative code.
This was the biggest refactoring and engine improvement I've done ever. I'm very glad I did this, because now I can make stuff faster, easier and there'll be a lot less bugs in the future. Some people prefer to create workarounds for the problems they have, but if I did this, the game would be buggy, the engine would be a pain to work with and my creativity would be very limited by the stuff I coudn't implement because of the engine structure. So, I'm satisfied with most of the stuff I've remade and now I'm working on the game itself most of the time.

What about the game?
Here's a gif to get you interested. Not much have changed, but here you can see, that I've created bigger rooms for buildings, made scrolling a lot smoother etc.. The talking animation also stops when the character finishes the part he's talking.


This post will give a brief overview about what I've done and I'll write a more detailed post later.

Separating drawing, input, event handling
This started when I tried to implement fixed time step. I've realized, that in my game input, drawing and other stuff wasn't separated well enough, so implementing fixed time step wasn't really possible. "SFML Game Development" book gave me some awesome ideas about this and stack of game states, so I did it mostly as described there. This also helped me get rid of lots of global variables. For example, window was a global variable, but now I can pass it in draw function as an argument from the main loop. The main loop looks like this now:
Code: (cpp)
void Game::mainLoop() {
    sf::Time delta = mainClock.restart();
    timeSinceLastUpdate += delta;
    while (timeSinceLastUpdate > TimePerFrame) {
        timeSinceLastUpdate -= TimePerFrame;
        processEvents();
        update(TimePerFrame);
    }
    draw(window);
}
This is as simple as it can get and it feels great. :D
Using stack of GameStates helped me fix some bugs and improve code readability a lot.

EventManager
I've talked about this one before, but let's look at it again. Now I can send events like this:
Code: (cpp)
Event event("HpChangedEvent");
event.setData("hp", healthComponent->getHp());
eventManager.queueEvent(event);

Subscribing to events looks like this:
Code: (cpp)
eventManager.addListener(this, "HpChangedEvent", &GUIPlayingState::onHpChanged);
In this example, GUIPlayingState wants to know when player's hp changed, but other entities can send this event too, so GUIPlayingState can subscribe to the player entity only:
Code: (cpp)
eventManager.addSubjectToListener(this, "HpChangedEvent", playerEntity);

Now, when the HpChangedEvent is triggered, onHpChanged callback is called by EventManager. It looks like this:
Code: (cpp)
void GUIPlayingState::onHpChanged(const Event& event) {
    playerHp = event.getData<int>("hp");
    ... // update GUI!
}
I can also send events from Lua scripts!
Code: (lua)
event = {}
event.hp = entity:getHp()
event.type = "HpChangedEvent"
queueEvent(event)
Event Lua table is converted to C++ event and then is queued in EventManager. The most cool part is that no matter what sends the event, Lua or C++, both Lua and C++ listeners' callbacks will be called! When the Lua callback is called, I can do stuff like this:
Code: (lua)
someLuaCallback = function(event)
    print("Hp changed to " .. event.data.hp)
end
I previously used EventType enum for event types, but I now use string hash ids, so I don't have to recompile a lot of stuff when I add a new event. But this raises another question: what if I mistype something? Like this:
Code: (cpp)
Event event("HpChangeEvent"); // should be HpChangedEvent
No problem, I will check the string hash and if it wasn't registered before (all event types are registered during EventManager initialization) nice little assert will tell me about this (crashing my game which is good, because I'd have to fix the error immediately!). (I would love to have some static_assert for checking this, but is this even possible?)

Event class is not a base class for all event types and it's great. It looks like this:
Code: (cpp)
class Event {
    ...
    std::map<std::string, EventArg> args;
}

What is EventArg? It's a cool class which has an unrestricted union which may contain everything that I need for events: bool, int, float, string, etc. I can easily cast it to the needed type like this:
Code: (cpp)
auto someInt = eventArg.cast<int>();

args map is easily converted to Lua table and vice versa, which is what makes using events easy with Lua. I'm very satisfied with the event system now.

C++11/14 stuff
C++11 makes your code much better. Back when I started making Re:creation, some C++11 features were not known to me or the Visual Studio version I was using didn't support these features. Now I've moved to VS2015 and now most of the cool stuff is available to me. Let's talk about the stuff which is available for most people first:

* Pointers
Mostly I was using shared_ptrs everywhere, but then I've realized that I was thinking about smart pointers wrong. Using raw pointers is okay as long as they are non-owning! So, it's better to pass a raw pointer to a function which does something with this pointer, than to pass unique/shared_ptr. So, I do it like this:
Code: (cpp)
void someFunc(SomeClass* ptr) {...}
...
std::unique_ptr<SomeClass> ptr;
...
someFunc(ptr.get());

And using unique_ptr is better than using shared_ptr most of the time. And it's best to not use pointers at all, so I tried to reduce the number of pointers to minimum and use unique_ptr where it's needed.
There were some problems with forward declaration, though. This will produce an error:
Code: (cpp)
// A.h
...
class B;
class A {
     ...
     std::unique_ptr<B> ptr;
};
Why? That's because the compiler creates default destructor in the A.h and B is not available yet, so the compiler tells you that it can't know how to delete B!
Here's an easy way to fix this:
Code: (cpp)
// A.h
...
class B;
class A {
public:
     ~A();
private:
     ...
     std::unique_ptr<B> ptr;
};

// A.cpp
#include "B.h"
A::~A() = default;
...
Good! But this creates another problem when you need move ctor/operator=.
Defining a constructor will prevent move ctor/operator from generation, so you have to declare them for yourself. You can use =default for them, but VS2013 can't do that (but VS2015 can!)

* auto
There's no need to explain how cool auto is. It helped me refactor stuff a lot, because I was using it a lot before, so I didn't have to manually replace a lot of code.

* for-ranged loop
Instead of writing stuff like this:
Code: (cpp)
for(auto it = someStuff.begin(); it != som
eStuff.end(); ++it) {
    auto& something = *it;
    ...
}
I can now write this:
Code: (cpp)
for(auto& something : someStuff) {
    ...
}
Saves a lot of typing and this is easier to write. Now I wish that stuff like this was possible (it will be possible in C++17, most likely!):
Code: (cpp)
for({someKey, someValue} : someMap) { ... }

* STL algoritms + lambdas
I didn't use algorithms much before, because writing functions and then passing them to another function wasn't fast. Lambdas make it a lot better. Suppose you have to find something which matches some condition. You can do it like this:
Code: (cpp)
auto foundIt = someVector.end();
for(auto it = someVector.begin(); it != someVector.end(); ++it) {
     auto& elem = *it;
     if(.../*condition*/) {
          foundIt = it;
     }
}

Or like this:
Code: (cpp)
auto it = std::find_if(someVector.begin(), someVector.end(), [](const SomeClass& elem) { return ...; /*condition*/ });
This is especially awesome when you need to erase something. Previously I had to do it like this:
Code: (cpp)
auto it = someVector.begin();
while(it != someVector.end()) {
     if(... /*condition*/) {
         it = someVector.erase(it);
    } else {
        ++it;
    }
}
You can't just do it in for loop, because erasing something will invalidate some iterators.
But now I can do it like this!
Code: (cpp)
std::erase(std::remove_if(someVector.begin(), someVector.end(),
     [](const SomeClass& elem) { return ...; /*condition*/}), someVector.end());
Much better.

One more thing, when you stuff with maps or vectors which store objects with long class names, lambdas can be pretty painful to write, because you have to do stuff like this:
Code: (cpp)
[](const std::pair<KeyClass, ValueClass>& elem) { ... }
Generic lambdas (supported in VS2015 now!) make it a lot easier:
Code: (cpp)
[](const auto& elem) { ...}
Awesome. If you can't use generic lambdas, the best thing I can come up with is this:
Code: (cpp)
[](const decltype(someMap)::value_type& elem) { ... }
Not pretty, but works.
Code: (cpp)
// end of C++11/14 stuff

Const correctness
Not too much to write about this, but I've improved const correctness a lot and this helped me fix a bunch of subtle bugs and will probably save me a lot in the future!

What, there's more?
Yeah, I have lots of stuff to write about (mostly about Lua). Lua enums, new cool LuaScriptManager, removing header dependencies (a.k.a. how LuaBridge caused compiler to recompile everything all the time), improving scripts and more! Stay tuned.

I would love to hear your thought about the stuff I've done and the stuff which you do to improve the quality of your code.
Logged
eliasdaler
Guest
« Reply #62 on: November 26, 2015, 01:39:04 PM »

Today I've tried to see how the prison/dungeon would look like if drawn in the new perspective. And the results are pretty cool. I'll show them when I implement them in the game! (It's mostly a sketch now)

And today's Pixel Dailies theme is Bard, so I've drawn Undead Bard who is a cool character which you see from time to time on screenshots Smiley
Logged
eliasdaler
Guest
« Reply #63 on: November 28, 2015, 02:49:50 AM »

I created pretty cool shadow system


Previously shadows were drawn by hand, but I realized that this is pretty boring to do, so I created a simple shadow system. Here's how it works:


First, I apply a simple fragment shader to the object's silhouette.
Here it is:
Code: (glsl)
uniform sampler2D texture;

void main()
{
    float alpha = texture2D(texture, gl_TexCoord[0].xy).a;
    if(alpha < 0.1) {
        discard;
    }
    vec4 color = vec4(0.098, 0.098, 0.098, 0.5);
    gl_FragColor = color;
}
So, I just set all non-transparent pixels to one color.

Then I skew the silhouette using sf::VertexArray (this is done by simply moving two top verticles (marked as 0 and 1)  like this:


and then I draw the original sprite on top of the shadow. That's it! I still don't know how much I have to skew each shadow to get the best looking shadows (anyone has ideas?). Maybe there are ways to make this even cooler? (I don't want to make anything complex, there won't be "true" lightning system in my game, no directed lights, etc.)

Another aspect of shadows is interaction with other objects and I'll write about it when I implement it (basically it's per-pixel collision test with shadow and object's collision rect)
Logged
eliasdaler
Guest
« Reply #64 on: November 28, 2015, 11:56:55 PM »

Okay, it's time to write another part about the refactoring I've done. It's also contains the hardest part of the refactoring!
Here's some small stuff I've done:

1. Lua enums. I've written about those already. They work pretty good
2. LuaEntityHandle. Read more about this technique here.
This thing allows me to write stuff like this:
Code: (lua)
entity:setAnimation("walk")
instead of
Code: (lua)
setAnimation(this, talk)
A minor change, but the one I like very much, because it makes scripts easier to read
3. Lua/C++ events. Written about it here.
4. And now for the hardest part. I didn't really think too much about header dependencies because I try to not include stuff in headers, use forward declaration as possible, try to decouple classes, etc. But changes in some classes caused lots of recompile for no reason. You now, like file changing A.h caused B.cpp which didn't include A.h in any way to recompile. I've searched for hours for the root of the problem. I've created a big graph of includes in my code but found no reason for this thing. And here comes the worst part: when I created new functions and registered them in LuaBridge, this caused almost full recompilation of the whole project (which took 4-5 minutes!). This was not acceptable. This would limit me a lot, because I wouldn't want to add C++ functions which can be called for Lua or change some classes which caused a lot.

And then I found it. It was LuaBridge.h. I created a quick project to test things out. So, suppose you have two classes A and B which are not related in any way. You register A class in A.cpp and do some stuff with LuaBridge in B.cpp. So, A.cpp and B.cpp have LuaBridge.h included in them. And now suppose you make some changes in class A and compile. B.cpp compiles too! What? This is what LuaBridge does. I think that compiler generates some weird template code again which causes LuaBridge.h to recompile causing ALL files which include LuaBridge.h to recompile.

As you may imagine, lots of files in my project included LuaBridge.h. This is what caused the problem! And I've found the solution. It was to make LuaScriptManager which included LuaBridge.h in LuaScriptManager.cpp but not in LuaScriptManager.h, so if you change some stuff and LuaBridge.h needs to recompile, only LuaScriptManager.cpp is recompiles, but all the files which use LuaScriptManager.h are not recompiled!
But making good interface for LuaScriptManager was a big challenge. There were two problems:

Problem A. I needed to make some templated functions which used LuaRef in them (like T getData<T>). This meant that I needed to include LuaBridge.h in LuaScriptManager.h which would cause the same problems again!

Here's how I solved it:
You can declare a template function in .h but define it in C++ if you do template specialization. But this proved to be bad, because a lot of the code had mostly common stuff in it and I didn't want to just copy-paste stuff. And then I've found out about one of the coolest template features ever. It's called explicit template instantiation. So, all you have to do is to define the template function in .cpp and then put explicit instantiations in .cpp for all the types you want this function to use with. This limits you to only those types, but I was okay with that, because getData was used with only a number of classes like int, float, bool, etc.
So, here's how the code looks:
Code: (cpp)
// LuaScriptManager.h
class LuaScriptManager {
    ...
    template <typename T>
    T getData<T>(...);
    ...
};

Code: (cpp)
// LuaScriptManager.cpp
...
template <typename T>
T LuaScriptManager::getData<T>(...) {
    // template code
}

// explicit instantiations
template bool LuaScriptManager<bool> getData(...);
template int LuaScriptManager<int> getData(...);
... // etc.
So, this causes compiler to generate all this functions in .cpp file, so you don't have to have template function definition in your .h file which enables you to not put code into headers.

Problem B. I needed to find some wrapper around LuaRef which could act like LuaRef, but didn't require files where it's used to include LuaBridge.h. This one was easy, I just created class like this:
Code: (cpp)
namespace luabridge {
    class LuaRef;
} // forward declaration

class LuaRefWrapper {
    template <class T>
    T cast() const;
    ...
    std::unique_ptr<luabridge::LuaRef> refPtr;
};
This also allowed me to get rid of some explicit template instantiations in LuaScriptManager which made code a lot better. I can just use LuaRefWrapper.h in LuaScriptManager.h without including LuaBridge.h! So, I got rid of the dependency, but I also got a few good things out of LuaScriptManager too, of course.

1) Hiding stuff/error checking inside the scripts
Suppose I have entity definition that looks like this:
Code: (lua)
someEntity = {
    ...,
    GraphicsComponent = {
        filename = "res/images/someEntity.png",
        ...
    }
}
Suppose I want to get someEntity.CollisionComponent.collide. Here's what I had to do previously:
Code: (lua)
auto entityTable = luabridge::getGlobal(L, "someEntity");
auto componentTable = entityTable["GraphicsComponent"];
auto filenameRef = componentTable["filename"];
std::string filename;
if(filenameRef.isString()) {
    filename = filenameRef.cast<std::string>();
} else {
    filename = "res/images/error.png";
}
There are lots of things that can go wrong. What if one of the tables is not found? Should add checks for this too?
Here's what I can do now:
Code: (cpp)
auto componentTable = scriptManager.getTable("someEntity.GraphicsComponent");
auto filename = scriptManager.getData<std::string>("filename", "res/images/error.png", componentTable);
Much better! If one of the tables is not found, the program doesn't crash and LuaScriptManager tells me about the error. If the value is of the wrong type, the script manager tells me about this too. If there are some errors, getData returns the default value which I provided as the second argument.

2) Overwrite
One functionality I've implemented a long ago was entity "inheritance". Suppose I have one entity with lots of components. And now I decide to create almost the same entity, but with another sprite. Here's how I can do that:
Code: (lua)
base_entity = {
    ...
}

derived_entity = {
    template_init = function(this)
        this:copyEntity("base_entity") // copies everything from base_entity
    end,
    GraphicsComponent = {
        filename = "res/images/newSprite.png"
    }
}
The difference between default loading and this type of loading is that I don't care if the value is not found in derived entity. If this is a case, I just don't change the value. If the value is found, I replace it with a new one. And here's what I had to do previously:
Code: (cpp)
auto entityTable = luabridge::getGlobal(L, "someEntity");
auto componentTable = entityTable["GraphicsComponent"];
auto filenameRef = componentTable["filename"];
if(!filenameRef.isNil()) {
    filename = filenameRef.cast<std::string>();
}
Notice how it's almost the same as the previous example. Can I implement it better with new scriptManager? Sure
Code: (cpp)
auto componentTable = scriptManager.getTable("someEntity.GraphicsComponent");
auto filenameRef = scriptManager.getRef("filename", componentTable);
if(filenameRef.isNil()) {
    filename = filenameRef.cast<std::string>();
}
This almost looks like LuaBridge code above! So I came up with a better solution, now I can do stuff like this:
Code: (cpp)
scriptManager.getData(filename, "res/images/error.png", "filename", componentTable, isOverwrite);
Here's what's going on. Fist of all, notice that I don't have to specify template argument because of the type deduction which happens because I pass the first argument.
If isOverwrite == false, this works as getData<std::string> that I showed above. If isOverwrite == true, then I rewrite the value only if it's found. But I don't write about errors that some tables or values are not wrong, because some values may be missing in derived entities and that's fine.
So, this one line works for two cases: loading value expecting to find it and trying to overwrite value only if the value is found.

So, all this stuff lead me to great simplification of script loading code, allowed me not to worry about the errors in scripts and reduced a lot of header dependencies. This also allows me to replace LuaBridge with some other library in the future only by changing LuaScriptManager and not bothering about other code which is very good.

I'm very glad I did this epic refactoring/engine improvement. Now I'm a lot more confident in the engine and I'm already making some progress about the game. Smiley
Logged
eliasdaler
Guest
« Reply #65 on: November 30, 2015, 01:32:23 PM »

Today I've integrated new shadow system in the game (previously I used it in a test project). Here's how it looks!

Notice, that I don't draw shadows when I turn on the fog.

There's a lot of stuff to improve, of course. Here's one of such improvements:

The shadow on the left doesn't look as it should because the dirt/grass and tombstone were one sprite, so shadow system used all that sprite to create a shadow. But if I separate them and tell the system to generate a shadow only from the tombstone, it all becomes much better! (The shadow must be smaller though, but it's easy to change!)

Here's another screenshot for fun. There are still lots of things I have to make better (like don't skew shadows very much in some objects and there's other stuff) but I'm very satisfied with the result. The game looks more alive now!
Logged
Craigory Ham
Level 0
**



View Profile
« Reply #66 on: November 30, 2015, 03:54:07 PM »

Oh wow, this is so messed up (read, "Way neat").  It's such a fun idea, I can't believe this hasn't been done before. 

I'm curious, what was your inspiration for the body snatching mechanic?
Logged
eliasdaler
Guest
« Reply #67 on: November 30, 2015, 09:17:56 PM »

Oh wow, this is so messed up (read, "Way neat").  It's such a fun idea, I can't believe this hasn't been done before. 

I'm curious, what was your inspiration for the body snatching mechanic?
Thanks! Mostly it comes from Majora's Mask (transformation masks allowed you to gain new abilities and solve the puzzles). But in Majora's Mask you could transform everywhere you wanted, but I wanted to make another kind of puzzles which forced you to find a way to carry your original body forward and kill people in non-standard ways to be able to use their bodies to progress (like in a bridge puzzle).
Logged
JobLeonard
Level 10
*****



View Profile
« Reply #68 on: December 01, 2015, 02:34:12 AM »

The new system looks good! Although in the animated gif it has a higher resolution than the in-game sprites, but you seem to have fixed that in the other images (for which I am grateful - that kind of break-of-pixel-immersion tends to pull me out of a game)
Logged
eliasdaler
Guest
« Reply #69 on: December 01, 2015, 03:35:19 AM »

Thanks! I don't like when the games break the grid too. :D
To be fair, I still haven't fixed this (I just noticed it not long ago). So, screenshots where the grid is consistent are taken in 256x240 and upscaled 2x. If I create 512x480 window, I get hi-res shadows, I'll find a way to fix this.
Here's a screenshot with "good" shadows
Logged
JobLeonard
Level 10
*****



View Profile
« Reply #70 on: December 01, 2015, 04:00:05 AM »

Hmmm, much better Hand Thumbs Up Left
Logged
eliasdaler
Guest
« Reply #71 on: December 01, 2015, 04:50:53 AM »

Neat. :D

To everyone reading this thread:
If you love Re:creation, please vote for it, so maybe it wins Indie of The Year indiedb award!  Beg
Vote now!
Do it, so you can spend more time talking with this cat in the future
Logged
eliasdaler
Guest
« Reply #72 on: December 12, 2015, 07:37:22 AM »

I'm making a small animation tool, so I don't have to edit JSON by hand. Why don't I just use JSON editor? That's because I'll add animation preview window soon.

The coolest thing about it is that it's much more readable than JSON because I can display better variable names. Here's how this animation looks in JSON:

Code: (none)
{
    "directions" : null,
    "frame" :
    [
        0,
        64,
        32,
        32
    ],
    "frameTime" : 100,
    "isLooped" : false,
    "name" : "die",
    "numOfFrames" : 13,
    "offset" :
    [
        2,
        -2
    ]
},

I think I'll move most of the entity stuff (data) to JSON and use Lua for functions for the rest, so I may create Entity Creation Tool which will work the same way as Animation Tool and if I integrate it with my engine, I'll be able to see how entity would look like as I create it. Isn't it awesome? :D
Logged
eliasdaler
Guest
« Reply #73 on: December 13, 2015, 12:01:19 AM »


It's happening! This is Qt with SFML drawing! :D
Animations are not yet implemented, but as you can see Qt and SFML work very well together! Integrating them was very easy, here's what I did. Feel free to give me some tips on how to make stuff better and tell me your experience with Qt + SFML :D

SFMLWidget.h
Code: (cpp)
#ifndef SFMLWIDGET
#define SFMLWIDGET

#include <QWidget>
#include <SFML/Graphics/RenderWindow.hpp>

class SFMLWidget : public QWidget
{
public:
    SFMLWidget(QWidget* parent = nullptr);

    void processEvents();
    void processInput(int dt);
    void update (int dt);
    void draw();
private:
    QPaintEngine *paintEngine() const;
    void enterEvent(QEvent *) override;
    void paintEvent(QPaintEvent*) override;

    sf::RenderWindow window;
    sf::View view;
};

#endif // SFMLWIDGET

SFMLWidget.cpp

Code: (cpp)
#include "sfmlwidget.h"
#include <SFML/Window/Event.hpp>

SFMLWidget::SFMLWidget(QWidget *parent) :
    QWidget(parent),
    view(sf::FloatRect(0.f, 0.f, 128.f, 120.f))
{
    //Setup some states to allow direct rendering into the widget.
    setAttribute(Qt::WA_PaintOnScreen);
    setAttribute(Qt::WA_OpaquePaintEvent);
    setAttribute(Qt::WA_NoSystemBackground);
    setAttribute(Qt::WA_PaintUnclipped);

    //Set strong focus to enable keyboard events to be received.
    setFocusPolicy(Qt::StrongFocus);

    //Under X11, we need to flush the commands sent to the server to ensure that
    //SFML will get an updated view of the windows
#ifdef Q_WS_X11
    XFlush(QX11Info::display());
#endif

    window.create((HWND)winId()); //Create the SFML window with the widget handle
    window.setView(view);
}

QPaintEngine* SFMLWidget::paintEngine() const
{
    //We make the paintEvent function return a null paint engine. This functions works together with
    //the WA_PaintOnScreen flag to tell Qt that we're not using any of its built-in paint engines.
    return nullptr;
}

void SFMLWidget::enterEvent(QEvent *)
{
    setFocus();
}

void SFMLWidget::paintEvent(QPaintEvent *event)
{
    draw();
}

void SFMLWidget::processEvents()
{
    sf::Event event;
    while(window.pollEvent(event)) {
        ... // process events
    }
}

void SFMLWidget::processInput(int dt)
{
    ... // process input
}

void SFMLWidget::update(int dt)
{
    ... // update stuff
}

void SFMLWidget::draw()
{
    window.clear();
    ... // draw
    window.display();
}

And then you can use SFMLWidget as QWidget without any problems!
And now I need to figure out how to do game loop inside this stuff and I'll try to use it with my engine if this works out well :D
« Last Edit: December 13, 2015, 03:08:41 AM by eliasdaler » Logged
zarxto
Guest
« Reply #74 on: December 13, 2015, 12:03:37 AM »

Impressive.

There needs to be more alttp inspired vidia games out there and you just boosted up that possibility my friend.
Logged
eliasdaler
Guest
« Reply #75 on: December 13, 2015, 01:44:53 AM »

Impressive.

There needs to be more alttp inspired vidia games out there and you just boosted up that possibility my friend.
Thanks! You should also check out Eastward which looks pretty great. :D

Another progress update...
I'm writing a lot today :D
Managed to get a basic loop working.


Code:

Code: (cpp)
#include "mainwindow.h"
#include "sfmlwidget.h"

#include <QApplication>
#include <QTime>

int main(int argv, char *args[])
{
    QApplication app(argv, args);
    MainWindow window;
    window.show();

    SFMLWidget* sfmlWidget = window.getSFMLWidget();

    int timeSinceLastUpdate = 0;
    int TimePerFrame = 16;

    QTime mainClock = QTime::currentTime();

    while(window.isVisible()) {
        app.processEvents();

        int delta = mainClock.restart(); // just like in SFML, lol
        timeSinceLastUpdate += delta;

        while (timeSinceLastUpdate > TimePerFrame) {
            timeSinceLastUpdate -= TimePerFrame;
            sfmlWidget->update(TimePerFrame);
        }
        sfmlWidget->draw();

        // add small sleep time, so we don't get delta == 0
        QTime dieTime= QTime::currentTime().addMSecs(1);
        while (QTime::currentTime() < dieTime) { } // wait until time passes
    }

    app.exit();

    return 0;
}

So, fixed-delta, pretty cool. But I have to add small sleep period (1 ms), or else delta will be 0 most of the time (Qt's timer is not very precise). Is it the best way to deal with this problem?
(And I need to figure out how to process SFML events for keyboard input, and then most of the work is done! :D)

Update: SFML events work well, I can easily get sf::Event::MouseEntered/Left to capture input
sf::Keyboard::isPressed works too! :D
« Last Edit: December 13, 2015, 01:58:41 AM by eliasdaler » Logged
eliasdaler
Guest
« Reply #76 on: December 14, 2015, 07:28:16 AM »


Okay, got animation on some basic introspection working. Here's how the wrapper looks.

Code: (cpp)
class AnimationWrapper : public QObject {
    Q_OBJECT
    Q_PROPERTY(bool looped READ isLooped WRITE setLooped)
    Q_PROPERTY(int frameCount READ getFrameCount WRITE setFrameCount)
    Q_PROPERTY(int frameTime READ getFrameTime WRITE setFrameTime)

public:
    AnimationWrapper(Animation* anim) :
        anim(anim)
    { }

    // is looped
    bool isLooped() const {
        return anim->isLooped();
    }

    void setLooped(bool b) const {
        anim->setLooped(b);
    }

    // frame count
    int getFrameCount() const {
        return anim->getFrameCount();
    }

    void setFrameCount(int fc) {
        anim->setFrameCount(fc);
    }

    // frame time
    int getFrameTime() const {
        return anim->getFrameTime().asMilliseconds();
    }

    void setFrameTime(int frameTime) {
        anim->setFrameTime(sf::milliseconds(frameTime));
    }
private:
    Animation* anim;
};
That's not the coolest thing, yeah, but now I can do this:
Code: (cpp)
animationWrapper->setProperty("frameCount", 10); // same as calling animation->setFrameCount(10); 
So, when some value changes, I find corresponding animation wrapper, and then I can change animation parameters using this wrapper.
Logged
eliasdaler
Guest
« Reply #77 on: December 14, 2015, 11:20:00 PM »

Okay, so I got some basic meta-property system working which lets me do this:
Code: (cpp)
class Animation : public MetaObject
{
public:
    Animation()
    {
        meta_register("name", name);
        meta_register("isLooped", isLooped);
        meta_register("frameCount", frameCount);
    }
private:
    std::string name;
    bool isLooped;
    int frameCount;
};

int main()
{
    Animation a;
    a.setProperty("name", std::string("walk"));
    a.setProperty("isLooped", true);
    a.setProperty("frameCount", 100);

    std::cout << "Name = "  << a.getProperty<std::string>("name") << std::endl;
    std::cout << "isLooped = "  << a.getProperty<bool>("isLooped") << std::endl;
    std::cout << "frameCount = " << a.getProperty<int>("frameCount") << std::endl;
}
Pretty neat! This will allow me to serialize to JSON /deserialize from JSON without any effort. One more thing I need to add is support for get/set member functions, but that's pretty easy to do, I just wanted to keep the code which I show brief.

Here's the source code:
http://pastebin.com/54ATGH9X
There's almost no error checking to keep the example brief.
Any suggestions to improve this are welcome! Maybe there's a better way do to it than to use dynamic_cast Tongue
Logged
eliasdaler
Guest
« Reply #78 on: December 15, 2015, 12:51:03 AM »

Thanks to Laurent's comments I created a better version of meta-stuff.
It was a bit harder to implement, but it's so much cooler!
http://pastebin.com/SgAr78X3

it works like this:
Code: (cpp)
class Animation
{
public:
    static void registerClass(MetaObject<Animation>& metaA)
    {
        metaA.addProperty("name", &Animation::name);
        metaA.addProperty("isLooped", &Animation::isLooped);
        metaA.addProperty("frameCount", &Animation::frameCount);
    }
    
private:
    std::string name;
    bool isLooped;
    int frameCount;
    FloatRect frame;
};

int main()
{
    MetaObject<Animation> metaA;
    Animation::registerClass(metaA);

    Animation a;
    metaA.setProperty(a, "name", std::string("walk"));
    metaA.setProperty(a, "isLooped", false);
    metaA.setProperty(a, "frameCount", 10);

    std::cout << "Name = " << metaA.getProperty<std::string>(a, "name") << std::endl;
    std::cout << "isLooped = " << metaA.getProperty<bool>(a, "isLooped") << std::endl;
    std::cout << "frameCount = " << metaA.getProperty<int>(a, "frameCount") << std::endl;
}
Of course, I'll need to have some MetaManager which will allow me to get Meta<Class> objects like this:
Code: (cpp)
auto& meta = metaManager.get<Animation>();
I'll implement it soon.

P.S. a small improvement:
Code: (cpp)
template <typename Class>
MetaObject::MetaObject()
{
     Class::registerClass(*this);
}

So now creating MetaObject<Animation> will automatically call Animation::registerClass
Logged
JobLeonard
Level 10
*****



View Profile
« Reply #79 on: December 15, 2015, 03:47:24 AM »

It's too early in the morning after a Ludum Dare all-nighter to comment on this Tired

... but I salute any fellow coder who cleans up his code Coffee
Logged
Pages: 1 2 3 [4] 5 6 ... 9
Print
Jump to:  

Theme orange-lt created by panic