Menu

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.

Show posts Menu

Messages - alexgleason

#91
Development / Multi-events as the default?
October 17, 2018, 08:41:47 PM
I was just having this moment of great realization for why Lua is a powerful programming language, then I hit a wall again.

To give an example, I have a script which does get_metatable("map"):register_event("on_started", ...)

The problem is, Solarus Quest Editor creates every map script with an empty on_started defined. So I'll need to edit all my maps and change it to map:register_event("on_started", ...) in order to make both functions run.

Basically, I'm trying to have events in "layers". Every map will always call the same "on_started" function in the metatable, but it will also call the "on_started" function of the specific map itself.

So I'm wondering if there should be a push towards using multi-event functions in Quest Editor instead. Eg, creating a new map might look like this:

Code ( lua) Select
-- Lua script of map test_map.
-- This script is executed every time the hero enters this map.

-- Feel free to modify the code below.
-- You can add more events and remove the ones you don't need.

-- See the Solarus Lua API documentation:
-- http://www.solarus-games.org/doc/latest
require("scripts/multi_events.lua")

local map = ...
local game = map:get_game()

-- Event called at initialization time, as soon as this map becomes is loaded.
function map:register_event("on_started", function()

  -- You can initialize the movement and sprites of various
  -- map entities here.
end)

-- Event called after the opening transition effect of the map,
-- that is, when the player takes control of the hero.
function map:register_event("on_opening_transition_finished", function()

end)


This would offer a lot more flexibility, because you could disable the default behavior by switching to an empty function. This seems like a more common use-case. I can't think of a use-case where it's better off the way it currently is.

The only real issue I see is that Quest Editor would have to assume you have a multi_events script. So then it makes me think... maybe package that into the engine itself? I mean, it's an essential tool for quest development.
#92
Video of this in action: https://vimeo.com/295527145

This is basically solving the problem of Banjo Kazooie's music notes (or Donkey Kong 64's bananas) - how do you save each individual banana/music note separately so that once you picked it up once, it is gone forever?

This script is unfinished, and it's only in a single map for now, but I wanted to share my progress. Later I'll clean it up and abstract it into a proper separate lua module.

My game has hundreds of pieces of trash on the map. When you load the map, the "beach_trash" savegame value looks like this:

1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111

After vacuuming 2 pieces of trash:

1111111111111111111111100111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111

After vacuuming 6 pieces:

1111111111111110011111100111111111111111111111111111111111111111110011111111111111101111111111111111111111111111111111111111111111111111111111111111111111111111111110111111111111111111111

Essentially, when the map loads, it finds all the trash entities, loops through them, and gives them an ID based on the order they're returned from map:get_entities(). So the first piece of trash returned from get_entities() would have an ID of 1 and so on. A "state" table then keeps track of every piece of trash. The trash's ID is used as a key for the state table, and the value is 1 if existing, and 0 if removed. The state table is converted into the above binary string when saved. Here's the full code:

EDIT: Better version in the post below. Also, someone pointed out that this is basically a bitmap. Pretty much - it's like removing all the gaps between the entities and then creating a bitmap over that.

Code ( lua) Select

-- ♡ Copying is an act of love. Please copy and share.

local map = ...
local game = map:get_game()
local trash_state = {}

-- Takes in an entity state table and converts it to a string for save
local function state_tostring(state)
  local chars = {}
  for i, v in ipairs(state) do
    table.insert(chars, tostring(v))
  end
  return table.concat(chars, "")
end

-- Takes a state string and returns a table
local function state_fromstring(str)
  if str == nil then return {} end
  local state = {}
  for i=1, #str do
    local n = tonumber(str:sub(i, i))
    state[i] = n
  end
  return state
end

-- Get all trash entities for a map
local function get_trash_entities()
  local trash_entities = {}
  local custom_entities = map:get_entities_by_type("custom_entity")
  for entity in custom_entities do
    if entity:get_model() == "trash" then
      table.insert(trash_entities, entity)
    end
  end
  return trash_entities
end

-- Assign ID's to all trash entities.
-- Add on_removed event.
local function process_trash()
  trash_state = state_fromstring(game:get_value("beach_trash")) -- load from save
  local entities = get_trash_entities()
  for i, entity in ipairs(entities) do
    entity.id = i -- Assign id to each piece of trash
    if not trash_state[entity.id] then
      trash_state[entity.id] = 1
    end
    function entity:on_removed() -- Update the trash state table
      trash_state[self.id] = 0
      local state_string = state_tostring(trash_state)
      game:set_value("beach_trash", state_string) -- Save the trash state
    end
    if trash_state[entity.id] == 0 then
      entity:remove()
    end
  end
end


function map:on_started()
  process_trash()
end

-- Remove the on_removed event, otherwise trash_state will be set to all zeros when the map is left
function map:on_finished()
  for i, entity in ipairs(get_trash_entities()) do
    entity.on_removed = nil
  end
end
#93
Your projects / Re: Ocean's Heart
October 17, 2018, 07:46:09 AM
Nice! I appreciate your thoughtfulness. You seem like a nice person. I'll just address the parts I have counterarguments for. I agree with a lot of what you wrote.

Quote from: MaxAs a small point, look how many Zelda fan games include a rooster statue in their village's central square, just because it's a nice looking asset to take from Link to the Past.

I realize you're using this as an example to illustrate a larger point, but I'm gonna talk about this literally for a sec. The rooster statue is a meme. I don't think reusing it is uncreative, it's just a cultural reference. But even if you don't think that, this isn't a Creative Commons asset anyway, it's an asset being copied without permission, which Nintendo could sue for.

People feel some ownership over old Nintendo graphics (rightly so). It was a part of a lot of people's childhoods. It's common for people to copy these graphics and use them in fan creations. It took us 26 years to get to that point with the rooster statue, though. Nintendo is a big company, and they can afford to prevent fans from spreading their work. As indie creators, I'm not sure we can afford not to encourage people to share and reuse our work. 26 years is too long.

I want to believe my game will become very popular. But the truth is that it probably won't. It has potential, but this is a competitive industry. Freeing the assets will at least give it a fighting chance for notoriety. It would be a worse mistake for me to spend so much time creating something that is relatively obscure and locked away from reinterpretation and reuse.

Quote from: MaxOr on a bigger scale, have an ice-themed area instead of a rainforest-themed area, just because that's the assets they have. If I want to encourage creativity, I don't think all my assets would benefit those who would use them.

These people are ignoring copyright anyway, so they have the whole world in their hands in terms of assets they could use. If they wanted to creatively reuse assets, there is virtually nothing standing in their way. They could have combined assets from Super Mario Bros and Portal. Most of them didn't, but the creator of mari0 did, and it certainly reinforced the cultural affection for both series.

I don't think being creative is an innate quality of a person, but there is a trend I can't ignore: some people are creative, and some aren't. A creative person can start with any arbitrary pool of resources and create something awesome. I do think we can teach people to more creative, but it would be a deep change in modes of thinking, not altering a resource pool. I mean, jack-o-lanterns involve literally carving a giant piece of fruit with a knife and look at what some people can do with that.

Quote from: MaxIf I'm thinking about re-using assets in future games, and assuming the best case scenario where I release graphics freely and a whole damn genre sprouts out around them, my next game might get lost?

This sounds like a great problem to have. I guess you'd just have to rise to the challenge and create a far more innovative game than before. :)

Quote from: MaxSo, obviously,  my logic isn't foolproof, but that's at least part of my rationale behind keeping a few assets out of any resource I create and share freely.

It sounds like you're a bit afraid to let go of the main character, and maybe the game's identity. Keep in mind that it can go both ways. If someone created a sequel to my game and I didn't have to lift a finger, I'd be thrilled. The only reason someone wouldn't is because of an egoistic attachment to the creation. It can be hard to separate the creation from the self, but doing so can be even more personally satisfying in the long term.

Quote from: Maxpile of orange peels

I dunno, I've searched for some very specific terms for free graphics ("trash bags", "vitamin") and was bummed there were no free graphics to go off of. I can't imagine that having more options would be a bad thing as long as sites like OpenGameArt.org are properly searchable and curated. You never know, maybe someone really needs a pile of orange peels, haha. I'd love a situation where everything you could think of has at least one free graphic online. Doesn't mean you have to put that graphic front-and-center, though. You could present generic graphics on a curated sprite-sheet, and just let people dig into your game's data to pull out whatever else they want. Up to you.

EDIT: Since we just had a whole conversation about a pile of orange peels, it has some cultural significance now, and I want to hide your pile of orange peels in my game somewhere only so the few people reading this thread can understand the inside joke.

EDIT2: Oranges are vegan.
#94
Your projects / Re: Ocean's Heart
October 16, 2018, 06:23:38 AM
Quote from: Max on October 16, 2018, 04:55:01 AM
My reasoning is I'd like to be able to maintain some identity for my game visually that's unique and marketable

I can definitely understand the concern - you don't want the art to become diluted and used in ways you didn't intend. I wouldn't want that, either. I've been thinking about this for a few minutes and I keep finding counterarguments, though. Like, if someone used your graphics in a crappy game nobody ever plays... oh well? Like, it might make me cringe, but realistically there's not much loss there. But if someone uses your graphics in a game that becomes very popular, then it's like "by the way, all these graphics were actually created for this great original game Ocean's Heart." Then there becomes a context and a history, your art spreads, and becomes imbued in culture.

I guess I just don't think spreading art around defiles its virginity. On the contrary, it helps it learn and grow. It frees it, and this is what free culture is all about.

The third possibility is that your art is proprietary and someone copies it and profits off it without giving you credit. This is always a possibility because some people don't follow the law. So, would you actually sue? The answer is "it depends" I'm sure, but nobody wants to sue. It's an extremely stressful and exhausting thing and most of the time you're better off just letting it go so you can continue to focus on building your own life up rather than tearing down enemies. Not to mention the public controversy of a lawsuit - nobody wants to join the ranks of Nintendo by suing over IP, not even indie game devs. If I'm totally misreading the situation, I apologize, I'm projecting my own situation onto this argument. But if you wouldn't sue if someone copied, and there's a lot to gain from people copying, and not much to lose from it, I personally am very much in the CC-BY boat. Anyway, that's my argument haha. Just wanted to share, but I respect your decision to do whatever you want. ;D Looks like it's gonna be a great game either way.
#95
Your projects / Re: Ocean's Heart
October 16, 2018, 01:25:46 AM
Wow, I just clicked through this, and your game looks amazing! Super exciting to see this much work put into a custom Solarus game.

Are you planning on freely licensing the game's graphics and code?
#96
Quote from: Christopho on October 15, 2018, 10:32:35 PM
You have this behavior because game:on_command_pressed() is called before the on_command_pressed() of game menus.

Okay, so it is intended behavior! Thank you for replying.

I thought the game is "below" the menu, and therefore it wouldn't be called. So is game:on_command_pressed() always called first no matter what when there's an active game?
#97
Okay, freezing and unfreezing the player was a bad solution because the hero must be unfrozen in order to brandish a treasure. So what I did instead to work around this is simply add this to game:on_command_pressed:

Code ( lua) Select
function game:on_command_pressed(command)
  -- Don't handle any of this stuff when a dialog box is open
  if game:is_dialog_enabled() then
    return false
  end
  ...
end


I'm getting the feeling that a "dialog box" is not simply just a normal game menu. It must have some additional behavior.
#98
I noticed something weird today: my sword can be activated during a dialog box. It goes to the first frame of animation and then is frozen until the dialog box finishes.



When I first opened the dialog box I checked "hero:get_state()" and it says "free". When I press the item button assigned to my sword, it says "sword activated". Keep in mind my sword is an item assigned to item_1 (using hero:start_attack() internally), so it's not surprising that I have strange behavior. But I did think it was strange the dialog box doesn't freeze my hero. I can fix this issue by calling hero:freeze() when the dialog box starts, and hero:unfreeze() when it finishes.

Okay, I understand that some menus (like a HUD) shouldn't freeze the player. But it raises a question: how exactly are button events handled? My dialog box has this:

Code ( lua) Select
function dialog_box:on_command_pressed(command)
  -- "action", "attack", "up", and "down" are handled here. I'm using "item_1" and not pressing any other buttons.
  ...

  -- Don't propagate the event to anything below the dialog box.
  return true
end


It returns true, so it shouldn't propagate. The context of dialog box is "game", and "game:on_command_pressed" is where the hero swings her sword.

In main.lua,

Code ( lua) Select
  -- I've simplified my code, but it shouldn't matter. This function shouldn't be called in the first place.

function game:on_command_pressed(command)

  print("you've reached me") -- This prints in the console when I perform the action in the screenshot, confirming that this function is called

  -- Makes the sword swing
  local item = get_item_for_command(command)
  item:on_command_pressed(command) -- this code calls hero:start_attack() internally

  return true
end


Any ideas why the event is propagated to game:on_command_press?? Thank you!
#99
Development / Re: User properties (key, value) ??
October 15, 2018, 07:15:26 PM
Looks like a new feature in Solarus 1.6: http://www.solarus-games.org/doc/1.6/lua_api_entity.html#lua_api_entity_get_property
Quote
User-defined properties are arbitrary key-value pairs that you can set to any entity. The engine does nothing special with them, but you can use them in your scripts to store extra information.
#100
General discussion / The perfect controller
October 15, 2018, 04:27:37 AM
I spent today working on controller input for my Solarus game, and it sparked me to write about my decades-long yearning for the perfect video game controller. Just wanted to share, article here:

https://blog.alexgleason.me/the-perfect-controller/
#101
Bugs & Feature requests / Re: Improve gamepad support
October 14, 2018, 10:03:14 PM
SDL_gamecontroller sounds like magic. Did this pull request ever happen?
#102
This is a FAR simpler solution. If you're willing to just accept that your game has only one save file, you can start it up first thing, then you'll be free to use on_command_pressed in your menus. Nothing else but this is needed in your main.lua:

Code ( lua) Select

function sol.main:on_started()
  local game = sol.game.load("save1.dat")
  game:start() -- This is where the magic happens

  sol.menu.start(game, solarus_logo) -- We're setting the menus to use the "game" we started above

  function solarus_logo:on_finished()
    sol.menu.start(game, title_screen) -- The title screen uses internal logic to reload the game once it's ready
  end

end
#103
Hey all, just wanted to share my progress with this. I ended up writing a script that gives default configs to any menu object:

scripts/enable_commands.lua
Code ( lua) Select

-- ♡ Copying is an act of love. Please copy and share.
--
-- Use this script to pass in a game or menu object and have it set up
-- default keyboard and joystick controls so you can use `on_command_pressed`
-- as expected.
--
-- Usage:
--    local enable_commands = require("scripts/enable_commands")
--    enable_commands.enable(my_menu)
--
-- Then simply define `function my_menu:on_command_pressed(command)`

local enable_commands = {}


local function on_key_pressed(self, key)
  if self.on_command_pressed == nil then return true end
  if key == "space" then self:on_command_pressed("action")
  elseif key == "c" then self:on_command_pressed("attack")
  elseif key == "d" then self:on_command_pressed("pause")
  elseif key == "up" then self:on_command_pressed("up")
  elseif key == "down" then self:on_command_pressed("down")
  elseif key == "left" then self:on_command_pressed("left")
  elseif key == "right" then self:on_command_pressed("right")
  elseif key == "x" then self:on_command_pressed("item_1")
  elseif key == "v" then self:on_command_pressed("item_2") end
  return true
end

local function on_key_released(self, key)
  if self.on_command_released == nil then return true end
  if key == "space" then self:on_command_released("action")
  elseif key == "c" then self:on_command_released("attack")
  elseif key == "d" then self:on_command_released("pause")
  elseif key == "up" then self:on_command_released("up")
  elseif key == "down" then self:on_command_released("down")
  elseif key == "left" then self:on_command_released("left")
  elseif key == "right" then self:on_command_released("right")
  elseif key == "x" then self:on_command_released("item_1")
  elseif key == "v" then self:on_command_released("item_2") end
  return true
end

local function on_joypad_button_pressed(self, button)
  if self.on_command_pressed == nil then return true end
  if button == 0 then self:on_command_pressed("action")
  elseif button == 1 then self:on_command_pressed("attack")
  elseif button == 4 then self:on_command_pressed("pause")
  elseif button == 2 then self:on_command_pressed("item_1")
  elseif button == 3 then self:on_command_pressed("item_2") end
  return true
end

local function on_joypad_button_released(self, button)
  if self.on_command_released == nil then return true end
  if button == 0 then self:on_command_released("action")
  elseif button == 1 then self:on_command_released("attack")
  elseif button == 4 then self:on_command_released("pause")
  elseif button == 2 then self:on_command_released("item_1")
  elseif button == 3 then self:on_command_released("item_2") end
  return true
end

local function on_joypad_axis_moved(self, axis, state)
  if axis == 0 then
    if state == 1 and self.on_command_pressed then self:on_command_pressed("right")
    elseif state == -1 and self.on_command_pressed then self:on_command_pressed("left")
    elseif state == 0 and self.on_command_released then
      -- FIXME: Only release the last command?
      self:on_command_released("right")
      self:on_command_released("left")
    end
  end
  if axis == 1 then
    if state == 1 and self.on_command_pressed then self:on_command_pressed("down")
    elseif state == -1 and self.on_command_pressed then self:on_command_pressed("up")
    elseif state == 0 and self.on_command_released then
      -- FIXME: Only release the last command?
      self:on_command_released("down")
      self:on_command_released("up")
    end
  end
  return true
end

-- Enable on an item
function enable_commands.enable(item)
  item.on_key_pressed = on_key_pressed
  item.on_key_released = on_key_released
  item.on_joypad_button_pressed = on_joypad_button_pressed
  item.on_joypad_button_released = on_joypad_button_released
  item.on_joypad_axis_moved = on_joypad_axis_moved
end

return enable_commands


Then just run your menu through this, either in main.lua or in your menu script itself, like so:

Code ( lua) Select

local enable_commands = require("scripts/enable_commands")

-- Ability to use game commands on these menus
enable_commands.enable(title_screen)

function title_screen:on_command_pressed(command)
  -- Handle commands here! No need to handle keys or joypad inputs.
end


It's a horribly verbose script! But it works. Normally the save file stores the controller config, so it creates a chicken and egg problem where you must create a save file before you can configure a gamepad, but need to go through the file select menu first. This partially works around that by assigning the Solarus default keyboard and joystick inputs to normal gamepad actions like "attack" and "action" etc. for a particular menu (the title screen).

What I think I'll actually end up doing is load the game before the title screen, and maybe let the player configure their input device here. My goal is to make the game playable on a platform like RetroPie where no keyboard exists. But this works for now.

As a side note, I think having 3 separate save files is overkill for most Solarus games. Unlike the days of Super Nintendo, most people have their own personal devices that can run Solarus, whether that's a laptop, desktop, Android phone/tablet, or Nintendo Switch. The exception might be a RetroPie device, but even then these often have hundreds of games and different people will probably be playing a different game during a different time. So, I'm going to focus on handling 1 save file at a time which will make things much easier.
#104
This script is useful for when your character receives the first item of the game. Rather than making them equip the item, you can make it automatically assigned to slot 1. Adding slot 2 support probably wouldn't be too difficult. Here's the script.

Code ( lua) Select
-- ♡ Copying is an act of love. Please copy and share.

require("scripts/multi_events.lua")

-- When the player obtains their first item, assign it to slot 1 automatically

local function item_obtained_cb(self, variant, savegame_variable)
  local slot_1 = sol.main.game:get_item_assigned(1)
  if slot_1 == nil and self:is_assignable() then
    sol.main.game:set_item_assigned(1, self)
  end
end

local item_metatable = sol.main.get_metatable("item")
item_metatable:register_event("on_obtained", item_obtained_cb)


Install it by creating the file scripts/item_auto_assign.lua in your project and pasting the above code.

Then, open scripts/features.lua and add this to the list:

Code ( lua) Select
require("scripts/item_auto_assign")

I am assuming you're using the Solarus engine boilerplate code, which already includes the multi_events script.
#105
Your projects / Re: Vegan on a Desert Island
October 12, 2018, 06:39:45 PM
I played Survival Kids for a few hours last night. It really is super similar to what I'm going for! I was surprised.

The way you can collect, use, and combine items was really illuminating. I think this will help me make some decisions about that. Thanks again. :D

I agree that it's too hard, though. I prefer to enjoy the game I'm playing instead of feeling like I'm constantly fighting for my life, haha.