Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Re:creation - a top down action adventure about undeads [hiatus]  (Read 157207 times)

0 Members and 1 Guest are viewing this topic.

Elias Daler

  • Hero Member
  • *****
  • Posts: 570
    • View Profile
    • Blog
    • Email
Re:creation - a top down action rpg about undeads
« Reply #330 on: October 24, 2015, 07:34:30 am »
Glocke, thanks for your code. :)
I think I'll need shaders to create cool effect when the player turns into a ghost. Still not sure how it will look like.

Progress update
So, I've been refactoring and improving the code this week. This actually feels very good, because I'm fixing a lot of bugs and improving the readability and structure a lot. I'll tell more about stuff I've been doing a bit later.

So, I have a question about one aspect of the code.

For a lot of time I could get components from entities with this function:
template <typename T>
T* Entity::get() {
    ...
    if(...) { // component found
        return componentPtr;
    }
   return nullptr; // component not found
}

And I could check if the component exists like this:
auto gc = entity->get<GraphicsComponent>();
if(gc) { // do stuff
     ...;
}

This is needed for two reasons:

1) Systems check if entities have components which they are interested in

2) C++ functions which can be called from Lua check this to send error messages if you call something like setAnimation(e, "anim") for entity which has no GraphicsComponent.

But what if I return the references instead?
template <typename T>
T& Entity::get() {
    ...
    if(...) { // component found
        return component;
    }

   ??? // component not found
}
 

The question is: what do I do in the situations where entities don't have the component? I think I can do it like this:
template <typename T>
T& Entity::get() {
    assert(...); // check if component is found
    ...
    return component;
}

And I'll have to check if entity has a particular component if I'm not sure if the components exist
if(e->hasComponent<GraphicsComponent>() {
    auto& gc = e->get<GraphicsComponent>();
} else {
    ... // do something else
}
 

So, I think this is better than getting nullptr errors where I don't check if component exists and it doesn't.
GraphicsComponent* gc = e->get<GraphicsComponent>(); // nullptr
gc->doSomething(); // crash!
vs
GraphicsComponent& gc = e->get<GraphicsComponent>(); // assert!
gc.doSomething() // component totally exists, because if assert aborted the program, we won't be there

Is this a good way to do things? Is there a better way to do it? Maybe returning the pointer to component is fine?
Tomb Painter, Re:creation dev | eliasdaler.github.io | @EliasDaler | Tomb Painter dev log

Glocke

  • Sr. Member
  • ****
  • Posts: 289
  • Hobby Dev/GameDev
    • View Profile
Re:creation - a top down action rpg about undeads
« Reply #331 on: October 24, 2015, 11:06:56 am »
Is this a good way to do things? Is there a better way to do it? Maybe returning the pointer to component is fine?
Personally, I'm using a pure get without extras! My getter returns a reference (or const reference if suitable), and I use a has()-Operation to query existence. Its great for readability: Prefering references over pointers (where pointers have to be non-null) is always good. So I always branche !has() to return early (e.g. no damage calculation if no health component) or do something alternative (e.g. writing to a log that the component is missing but shouldn't be ^^). Personally, I also use lots(!) of assertions, which help readability, "understandability" and "refactorability", as well as "debugability" ...
So the separation of has and get makes sense for my coding, because I'm asserting stuff anyway. But the separation can also be used without asking for the actual component, e.g. to check for logical paradox.. maybe an object has either a FooComponent or a BarComponent (not both, not none) ... this can be tested without asking for the actual components - if required.

So my code looks similar, like this:
assert(movesys.has(actor_id)); // ignored in production code
auto& movedat = movesys.get(actor_id); // safe in dev code
++movedat.pos.x; // etc.
or
if (!animationsys.has(actor_id)) { // also evaluated in production
    log << "Object << " << actor_id << " cannot be animated\n";
    return;
}
auto& anidata = animationsys.get(actor_id); // always safe
// do whatever

My getters have some assertions, too - to avoid memory violation. But they do not check anything in production code.. just deliver the component as fast as possible. If the code needs to change using existence, I use has additionally before querying a component.
« Last Edit: October 24, 2015, 11:22:20 am by Glocke »
Current project: Racod's Lair - Rogue-inspired Coop Action RPG

Elias Daler

  • Hero Member
  • *****
  • Posts: 570
    • View Profile
    • Blog
    • Email
Re:creation - a top down action adventure about undeads
« Reply #332 on: October 24, 2015, 11:34:07 am »
Glocke, awesome, thanks for explanation. :D
I was using A LOT of pointers before and I think I need to refactor this a bit because sometimes references are much nicer, yeah. As for assertions... I've been using them from time to time, but I was using console output for errors mostly.  I think I need to use them more to make my game more stable and to prevent some stupid bugs in the future. :D
Tomb Painter, Re:creation dev | eliasdaler.github.io | @EliasDaler | Tomb Painter dev log

Glocke

  • Sr. Member
  • ****
  • Posts: 289
  • Hobby Dev/GameDev
    • View Profile
Re:creation - a top down action adventure about undeads
« Reply #333 on: October 26, 2015, 11:56:00 am »
for assertions... I've been using them from time to time, but I was using console output for errors mostly. I think I need to use them more to make my game more stable and to prevent some stupid bugs in the future. :D

Personally, I prefer "testable" assertions (which to not abort while unit testing, but throw a specific exception that can be expected and caught) to guarantee (during unit testing) that specific conditions do not happen.

For error logging, I wrote a minimal wrapper using some operator<< and an assign() operation, which adds a logging target. So I can create a "Logger"-instance debug which can be used like
std::ofstream file{"debug.log"};
Logger debug;
debug.assign(file);
debug.assign(std::cout);
// ...
debug << "Position is " << myVector << "\n";
(with additional overloads for e.g. sf::Vector<> etc.) and write this to a specific log file and stdout. Both are some kind of basic output streams, so each call to debug's operator<< calls each assigned ostream's <<.

(My previous approach is part of my SfmlExt Lib, the new approach will be added as I have enough time to do it.)
« Last Edit: October 26, 2015, 12:00:09 pm by Glocke »
Current project: Racod's Lair - Rogue-inspired Coop Action RPG

Tank

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1491
    • View Profile
    • Tank's Blog
    • Email
Re:creation - a top down action adventure about undeads
« Reply #334 on: October 28, 2015, 09:29:38 pm »
I was facing the get/has/etc "problem" before as well. Two options:

1) Like Glocke said, split get() up into get() and has(), where get() contains an assertion (reason: calling get() without making sure that the object exists via has() is a programming error, so report to developer only).
2) Rename get() into find(). Find operations, by definition, can either return a hit or nothing. The return type would be a pointer in this case, and I think it's perfectly legit.

Personally I use find() wherever I can, because it's less error-prone and avoids code duplication (both get() and has() will query the same thing).

Btw, I wrote some lines about that topic on my blog.
SFGUI: Simple and Fast GUI library for SFML
Game Development Design articles
IRC: #sfml @ irc.boxbox.org

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6158
  • Thor Developer
    • View Profile
    • Bromeon
Re:creation - a top down action adventure about undeads
« Reply #335 on: October 28, 2015, 10:10:35 pm »
So my code looks similar, like this:
assert(movesys.has(actor_id)); // ignored in production code
auto& movedat = movesys.get(actor_id); // safe in dev code
Why not embed the assertion into get()? Makes code safer and more readable on call site.

You mention that you do it sometimes later, why not always? Getting something non-existing is always a mistake, as Tank pointed out.

1) Like Glocke said, split get() up into get() and has(), where get() contains an assertion (reason: calling get() without making sure that the object exists via has() is a programming error, so report to developer only).
Yes, exactly.

However, has() is an explicit case differentiation which may often be solved more elegantly using different design. If you end up having a lot of code such as:
if (e.has(thisComponent))
   doThis();
if (e.has(otherComponent))
   doSomethingElse();
...
you should think about restructuring the systems, so that code paths are selected depending on the available components a priori and implicitly.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development: first SFML book

Glocke

  • Sr. Member
  • ****
  • Posts: 289
  • Hobby Dev/GameDev
    • View Profile
Re:creation - a top down action adventure about undeads
« Reply #336 on: October 29, 2015, 08:17:38 am »
Why not embed the assertion into get()? Makes code safer and more readable on call site.
I placed an assert into both: get() and call site. This is because I faced problems while unit testing. My assertion macro expands to throwing a special exception in case of building the unit tests. Unfortunately I didn't get the configurations for boost test right (I'm not quite sure whether they even exist) to produce a call stack when an untested exception was thrown. So I get something like `AssertionFailed` thrown in component.hpp at line XY (that's the file which defines an abstract component system), which doesn't help my to find the right spot where I accessed an invalid object. When I move that assertion into the actual function (which is directly called by the corresponding unit test, so I don't need a callstack here), I'm able to figure out where my precondition has failed.
« Last Edit: October 29, 2015, 08:19:31 am by Glocke »
Current project: Racod's Lair - Rogue-inspired Coop Action RPG

Elias Daler

  • Hero Member
  • *****
  • Posts: 570
    • View Profile
    • Blog
    • Email
Re:creation - a top down action adventure about undeads
« Reply #337 on: October 29, 2015, 03:55:30 pm »
Thanks for bringing up some awesome points, guys. I especially agree on using find() vs get() with Tank. Tank's article is also very awesome. :D
I didn't know about _ASSERTE. This works much better than assert from assert.h. Not only because it doesn't execute functions in release build, but because it actually lets you see the call stack (with assert the call stack is just some random stuff). So, I guess I'll be using _ASSERTE in the future. :D

Progress update
I'm almost finished with refactoring. The structure of the engine is much better. I've also done some stuff like fixed-time step, better collision testing (spatial partitioning with grids) and some other stuff. I plan to write a blog post about the stuff I've done this Saturday or Sunday.

I also plan to finish demo prototype by the end of November. There will be some finished stuff and there'll be some placeholder art/NPCs/dialogue, etc.
The best case scenario is me finishing the demo by the end of this year, testing/polishing it for a month and then releasing somewhere by the end of January or somewhere near my birthday, ha-ha (which is on the 2nd of February).

And by the way, Re:creation is now 2 years old. Please, don't use this as a way to tell how much I have to work to finish the game. Now that I have a set of good tools and better experience with art, I'll be able to work much faster. Most of the work I've done during those two years was about creating the engine from almost a scratch. And I'm glad that I did, because now I know lots of stuff about game dev and have an awesome system which I'm quite proud of.
I'm almost done with the engine, there are some minor things I've yet to add, but they'll be implemented quite fast. Most of the game I have in mind can be implemented with the tools I already have. This makes me very happy.
So, the new era of Re:creation development will begin soon. Thank you for supporting me so far, I hope you'll get some cool stuff from my future work. :D

Tomb Painter, Re:creation dev | eliasdaler.github.io | @EliasDaler | Tomb Painter dev log

SeriousITGuy

  • Full Member
  • ***
  • Posts: 123
  • Still learning...
    • View Profile
Re:creation - a top down action adventure about undeads
« Reply #338 on: October 30, 2015, 11:03:04 am »
Congrats on keeping up the work for two years, can't wait to get my hands a copy of your game.

Elias Daler

  • Hero Member
  • *****
  • Posts: 570
    • View Profile
    • Blog
    • Email
Re:creation - a top down action adventure about undeads
« Reply #339 on: October 31, 2015, 10:52:35 pm »
Congrats on keeping up the work for two years, can't wait to get my hands a copy of your game.
Thanks

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++

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:

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:

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:

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. :)

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

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:

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:
Event e(EventType::HpChangedEvent);
e.setData("hp", 10);
eventManager.queueEvent(e);

and callbacks look like this:

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:

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)
Tomb Painter, Re:creation dev | eliasdaler.github.io | @EliasDaler | Tomb Painter dev log

JLM

  • Newbie
  • *
  • Posts: 8
    • View Profile
Re:creation - a top down action adventure about undeads
« Reply #340 on: November 11, 2015, 10:51:40 pm »
I have a question for you. How do you make objects like Arrows spawn?

Let's say I've got a EntityManager class which contains all of the Entities (Entities are ints).
And Systems (Singletons) which contain all Components (map<Enity id, Component>...).

So how do I allow a Component like PlayerInputController to add new Entity to EntityManager?
I can't just #include EntityManager to System becouse EnityManager #includes all The systems already.

I was wondering how you solved that problem.

Elias Daler

  • Hero Member
  • *****
  • Posts: 570
    • View Profile
    • Blog
    • Email
Re:creation - a top down action adventure about undeads
« Reply #341 on: November 12, 2015, 02:30:28 pm »
Well, here's how it works in my game.
1) Each item has ItemComponent with a reference to Lua script function called use
2) When the player or AI uses the item, this function is called.

It looks like this:
function use(this, owner) -- this - a pointer to item entity, owner - a pointer to owner entity
    local arrow = createEntity("arrow")
    setPosition(arrow, getPosition(owner))
end

createEntity is a global C++ function which can be called from Lua and it looks like this:
Entity* createEntity(const std::string& name) {
    auto entity = entityManager.create(name);
    return entity;
}

If you don't use scripts, you can use events and send event like "EntityCreateEvent" with a name of entity which needs to be created to the entity manager. (But implementing events is a bit harder, yeah)
Another way is to store a pointer to EntityManager in your system (this is not very good, because of coupling, but this works). You can prevent cyclic dependencies by using forward declarations.

Progress Update coming soon!
I've remade lots of script and engine stuff and this is very cool and will improve my perfomance a lot. More details today or tomorrow!
« Last Edit: November 22, 2015, 09:49:53 pm by Elias Daler »
Tomb Painter, Re:creation dev | eliasdaler.github.io | @EliasDaler | Tomb Painter dev log

Elias Daler

  • Hero Member
  • *****
  • Posts: 570
    • View Profile
    • Blog
    • Email
Re:creation - a top down action adventure about undeads
« Reply #342 on: November 22, 2015, 09:48:58 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:
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:
Event event("HpChangedEvent");
event.setData("hp", healthComponent->getHp());
eventManager.queueEvent(event);

Subscribing to events looks like this:
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:
eventManager.addSubjectToListener(this, "HpChangedEvent", playerEntity);

Now, when the HpChangedEvent is triggered, onHpChanged callback is called by EventManager. It looks like this:
void GUIPlayingState::onHpChanged(const Event& event) {
    playerHp = event.getData<int>("hp");
    ... // update GUI!
}
I can also send events from Lua scripts!
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:
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:
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:
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:
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:
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:
// 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:
// 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:
for(auto it = someStuff.begin(); it != som
eStuff.end(); ++it) {
    auto& something = *it;
    ...
}
I can now write this:
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!):
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:
auto foundIt = someVector.end();
for(auto it = someVector.begin(); it != someVector.end(); ++it) {
     auto& elem = *it;
     if(.../*condition*/) {
          foundIt = it;
     }
}

Or like this:
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:
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!
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:
[](const std::pair<KeyClass, ValueClass>& elem) { ... }
Generic lambdas (supported in VS2015 now!) make it a lot easier:
[](const auto& elem) { ...}
Awesome. If you can't use generic lambdas, the best thing I can come up with is this:
[](const decltype(someMap)::value_type& elem) { ... }
Not pretty, but works.
// 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.
« Last Edit: November 22, 2015, 09:52:09 pm by Elias Daler »
Tomb Painter, Re:creation dev | eliasdaler.github.io | @EliasDaler | Tomb Painter dev log

Ungod

  • Newbie
  • *
  • Posts: 44
    • View Profile
Re:creation - a top down action adventure about undeads
« Reply #343 on: November 26, 2015, 10:20:01 pm »
I'm following your project for some time now and i finally manged to register here. Your game and engine were a nice source of inspiration for me.

I really like your modding philosophy and your lua-scripting and editor tools related to that. Until now, I just hardcoded stuff, but I tried scripting in the past a few times. (I can remember that I tried lua for an afternoon, but I had some problems with the setup)

I think you have to consider carefully when using scripts, because they can really slow down the game if used for too basic procedures. Where do you use them? Just everytime you want to implement gameplay behavior, thats unique to certain object and will not occur regular in the game?

Elias Daler

  • Hero Member
  • *****
  • Posts: 570
    • View Profile
    • Blog
    • Email
Re:creation - a top down action adventure about undeads
« Reply #344 on: November 26, 2015, 10:35:18 pm »
Ungod, thanks a lot! Glad you find my articles/dev logs useful! :)

Yes, you have to use scripts for stuff that isn't computationally expensive and doesn't happen very often. For example, collision checking and resolution should be done in C++. But reaction to collision may be scripted, because collisions don't happen all the time and you're pretty safe. There are two things that should take most of the time if you're doing stuff right: collision, AI and rendering. Rendering and collision is done in C++. Some of the AI should be done in C++ (finding the shortest path, A*, etc.) and some can be scripted (going from one AI state to another, deciding what to do next, etc.). I don't optimize my script usage for now and I didn't notice any perfomance problems with Lua.

Progress update
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 :)
Tomb Painter, Re:creation dev | eliasdaler.github.io | @EliasDaler | Tomb Painter dev log