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

Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Messages - iocpu

Pages: [1]
1
SFML projects / My attempt at SFML with Lua scripting
« on: November 24, 2016, 05:00:22 pm »
So just like everyone else, I too have been working on my own 2D game for a few months now. Partially to practice with C++ meta-programming and partially for fun (although SFINAE is lots of fun too). Also, I wanted to create a complete game for once and I think chances are good that it will happen this time! (well, some time)

I'd like to show you how I handled scripting. As the title suggests, the language I picked is Lua... LuaJIT 5.1 to be precise.



For starters, entities. From Lua's point of view, there are only two types of entities: characters and cameras. They share some common behavior (such as movement) but obviously cameras are composed of less components.

While default values for entity attributes are defined in an external JSON file, most of them can be easily overriden by Lua at runtime. Attributes can be accessed and modified with the use of a dot operator, for example:

player.x = 100                -- sets player's X coordinate to 100

local new_y = player.y + 100
player.y = new_y              -- increases player's Y coordinate by 100

print(player.is_moving)       -- prints out whether player is currently moving

There are obviously lots of attributes I can get and set, including entity's collision box offset, current direction or even whether its emitting light.



Methods are used purely for actions an entity can do, such as movement or talking. They can be chained together in order to create a process queue, such that one action will take place after another in a given sequence, for example:

player:move(player.x, player.y + 200)
      :wait(1000)
      :move(player.x + 100, player.y + 200)  -- will first move south, then wait one second, then move east



Each entity has two process queues: one for primary and one for secondary processes. Some behaviors, such as moving or waiting are primary, meaning that if I chain move and wait, wait will happen after move finishes. However, actions such as talking are secondary, so that they can happen simultaneously with primary actions, for example:

player:move(player.x + 150, player.y)
      :talk("hello")
      :move(player.x, player.y)  -- will move east and say text of ID "hello" at the same time, afterwards will move back immediately regardless of whether it's still talking



To make it more complete, entities can also react to certain events that happen to them, such as collision or being triggered by another entity. For example:

player.on_collided = function(entity_id)
    print("Collided with entity of ID " .. id)  -- will print on collision
end

It's also possible to set callbacks for responding to whenever a process starts or ends, for example:

player.on_movement_ended = function(complete)
    if complete then
        player:play_sound("movement_complete.ogg")  -- will play sound if movement process completed successfully
    end
end

This allows for creating complex chains of actions for each entity individually and it's what is needed most of the time. However, the problem arises when creating a cutscene and trying to make one entity do something after another has finished (for example, make entity2 say something after player has moved).

Though this could be achieved with callbacks to on_movement_ended and similar, it would be awfully inconvenient to write all of that boilerplate code every time just to show a simple cutscene. Not to mention that if there was already a callback stored, it would have had to be saved first and then re-set after the cutscene. This is why I created a simple convenience object, simply called Cutscene, for situations just like these. In addition to coordinating actions between different entities, it also disables player's controls, darkens the display and ensures that only one cutscene can be played at any given time.

Cutscene:play(
    { Action.Move, player, player.x, player.y + 300 },
    { Action.Talk, entity2, "hello-player" },
    { Action.Wait, entity2, 1000 },
    function()                     -- I can add arbitrary functions too
        camera.x = player.y
        camera.y = player.y
        camera:record()
    end,
    { Action.Move, camera, entity2.x, entity2.y },
    { Action.Move, entity2, entity2.x - 200, entity2.y }
)





There are also ways to interact with display, audio and interface directly from Lua, for example:

Audio:play_music("theme-music.ogg", true)       -- will play theme music, 'true' will do a fade-in effect
Display.post_processing = PostProcessing.Sepia  -- this is why all my gifs are in sepia :)
Interface:show_message("hello")                 -- will show a pop-up for user to confirm



There are obviously more methods and attributes I can set for devices, though perhaps I will talk about them another time.



At last, there are two more things I wanted to show: event dispatching and save data store.

When writing demo scripts I quickly realized that anything more complex than a very simple game would lead to a lot of coupling and direct dependencies between various parts of the script. That's especially true if action of one entity should cause some other entities to react accordingly. So instead of using global variables and functions all over the codebase, I decided to use an observer pattern by implementing a centralized event dispatcher.

local function callback(what)
    print("Something interesting happened " .. what)
end

Event:subscribe(event.SOMETHING_INTERESTING_HAPPENED, callback)  -- registers the callback

...

-- somewhere else in the code; will notify all subscribers registered for this type of event
local subscribers_called_count = Event:raise(event.SOMETHING_INTERESTING_HAPPENED, "some interesting info")

As far as saving data goes, I wanted to make it very straightforward. There is a globally accessible save data store, which is basically a Lua table that provides a getter and setter for key-value pairs. I can read and write from/to the store as many times as I want but the data is only written to the disk when changes are actually commited (via an explicit method call). So if I want to operate on a value that should be persisent across play sessions, I should read it and write it from/to the save object directly.

-- retrieves progress from save; if nil, provided default value will be returned instead
local story_progress = Save:get(KEY_STORY_PROGRESS, DEFAULT_STORY_PROGRESS)

if story_progress == STORY_PROGRESS_BEGINNING then
    show_some_intro_cutscene()
    Save:put(KEY_STORY_PROGRESS, STORY_PROGRESS_EARLY_GAME)  -- updates progress

    Save:commit()                                            -- writes current contents of save to disk
end

As I was playing around with demo scripts, I realized that most of the times the type of data I was writing to save file were characters' attributes. Moreover, as characters are usually very dynamic and their states change frequently, I needed a way to easily update values associated with entities whenever the data was written to the disk. At first I tried with functions that would serialize each entity and add it to the save file. The problem was, I needed to call these functions every time before calling the commit method. This was not only error-prone but created unnecessary coupling between various parts of the code. Eventually I decided to implement a method that would register an entity and then serialize it and save automatically whenever commit method was called.

Save:register_character(KEY_PLAYER, player)

...

-- somewhere else in the code
Save:commit()  -- player's entity data within the save is automatically updated before changes are written to the disk

Loading a character from save can be accomplished with a single method call too.

Save:read_character(KEY_PLAYER, player)  -- loads player's state from save data



That's all for now. I hope the post wasn't too boring and that perhaps some approaches presented here will be useful to other projects too.

Oh, and one last thing - I didn't do the art. I mean, I wish I could draw like that, but for the time being I'm focused exclusively on the programming side. The sprites and textures come from Lost Garden and OpenGameArt.

Thanks for attention!

2
General / Re: Learning sfml and have a question
« on: November 19, 2016, 07:59:55 pm »
Can't really recommend any videos, as I personally prefer books. There are, however, lists of videos on things like C++ and Game Development (and much more) on GitHub. Perhaps you'll find something useful there?

As for SFML, I guess the official book would be a good start. In case you want to dig deeper into game development, I recommend this and this book too.

3
Graphics / Re: How protect the game's image files?
« on: November 19, 2016, 02:50:54 pm »
Store key xor'ed with (for example) 'g', now getting valid key will require both stored 'key' and algorithm used to create proper one.
I currently don't store a key at all - but rather calculate it at runtime from known constants. Obviously, anyone with a disassembly, debugger and enough time can easily extract the key just before it's used, though it's still a fun exercise to play with obfuscation.
You can't fight someone with disassembler, debbugger and time at all without encrypting whole binary. :D And even then… well, give up.
Yeah, the only secure way would be to store the key on an HSM or a smart card and then require the user to plug it in every time before the application starts :D But even then, as mentioned before, it would be possible to dump the resources from RAM (or vRAM) the moment they are stored as plaintext...

4
Graphics / Re: How protect the game's image files?
« on: November 19, 2016, 02:31:13 pm »
Store key xor'ed with (for example) 'g', now getting valid key will require both stored 'key' and algorithm used to create proper one.
I currently don't store a key at all - but rather calculate it at runtime from known constants. Obviously, anyone with a disassembly, debugger and enough time can easily extract the key just before it's used, though it's still a fun exercise to play with obfuscation.

5
General / Re: Learning sfml and have a question
« on: November 19, 2016, 11:36:11 am »
May I ask which YouTube tutorial are you watching? One thing to remember about programming tutorials in general is that they would often present a "quick and dirty" way to implement something because their goal is to explain the concept and not to dwell on the details (or good OOP practice for that matter).

6
Graphics / Re: How protect the game's image files?
« on: November 18, 2016, 09:40:15 pm »
Just wondering, how do you store the encryption key? I've recently implemented something similar - that is, loading encrypted archives into memory upon initialization and decrypting them with AES (I'm not using the .ZIP's built-in encryption though). I'm currently calculating the key at runtime as I didn't want to store it as plaintext within the binary - but perhaps there's a way to obfuscate the process even further?

7
General discussions / Re: SFML 2.4.1 released
« on: November 17, 2016, 11:20:00 pm »
The current LTS version. 16.10 already has them, so hopefully it wouldn't take long for the LTS to upgrade.

8
General discussions / Re: SFML 2.4.1 released
« on: November 17, 2016, 09:02:03 pm »
Ah, didn't know it's maintained by somebody else. Thanks for the quick reply.

9
General discussions / Re: SFML 2.4.1 released
« on: November 17, 2016, 07:33:23 pm »
Any news on when will the 2.4+ version be available from Ubuntu's official repo?

10
SFML projects / Re: Screenshot Thread
« on: November 17, 2016, 07:16:07 pm »
I've been working on a simple 2D game in my spare time. It's still quite bare-bone but the core is mostly "done". For now, the art I'm using comes from websites like Lost Garden and OpenGameArt. All multimedia-related stuff is accomplished with pure SFML.








Pages: [1]