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

Topics - alexgleason

#1
Your scripts / Pushable block reimplemented in Lua
June 29, 2019, 05:15:32 PM
Daniel and I wanted to create pullable/pushable entities that aren't blocks. Below is a Solarus block entity reimplemented in Lua. It's almost an exact copy, except that when pushing, the hero does not continually display the "pushing" animation (only between movements). It's a minor issue but I'd appreciate any help to solve it. Here's the code:


-- Lua script of custom entity block.
-- By Daniel Molina and Alex Gleason, licensed under GPL-3.0-or-later

require("scripts/utils")

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


-- Moves an entity in the 16x16 grid
local function grid_movement(d4)
  local m = sol.movement.create("straight")
  m:set_max_distance(16)
  m:set_speed(30)
  m:set_angle(d4_to_angle(d4))
  return m
end

-- Event called when the custom entity is initialized.
function entity:on_created()
  self:set_traversable_by(false)
  self:set_can_traverse(false)
  self:set_drawn_in_y_order()
  self._cooldown = sol.timer.start(self, 0, function() end)
end

-- Handle the hero pushing/pulling the entity
function entity:on_update()
  local hero = game:get_hero()
  local d4 = hero:get_direction()

  if not self:can_move() then
    return -- skip
  end

  if self:is_being_pushed() then self:push(d4) end
  if self:is_being_pulled() then self:pull(invert_d4(d4)) end
end

-- Push the entity
function entity:push(d4)
  log("Entity is being pushed")

  -- Set hero state
  local hero = game:get_hero()
  hero:freeze()
  hero:get_sprite():set_animation("pushing")

  -- Move entity
  local m = grid_movement(d4)
  function m:on_obstacle_reached()
    entity:stop_movement()
    hero:unfreeze()
  end
  m:start(self, function()
    entity:stop_movement() -- HACK: solarus-games/solarus#1396
    entity:snap_to_grid()
  end)

  -- Move hero
  local m_hero = grid_movement(d4)
  m_hero:set_smooth()
  m_hero:start(hero, function()
    hero:unfreeze()
    hero:start_grabbing()
    entity:start_cooldown()
  end)
end

-- Pull the entity
function entity:pull(d4)
  log("Entity is being pulled")

  -- Set hero state
  local hero = game:get_hero()
  hero:freeze()
  hero:get_sprite():set_animation("pulling")

  -- Move entity
  local m = grid_movement(d4)
  m:start(self, function()
    entity:stop_movement() -- HACK: solarus-games/solarus#1396
    entity:start_cooldown()
    entity:snap_to_grid()
  end)

  -- Move hero
  local m_hero = grid_movement(d4)
  m_hero:set_smooth()
  function m_hero:on_obstacle_reached()
    hero:unfreeze()
    hero:start_grabbing()
    entity:stop_movement() -- HACK: solarus-games/solarus#1396
  end
  m_hero:start(hero, function()
    hero:unfreeze()
    hero:start_grabbing()
  end)
end

-- Check if hero can move the entity (boolean)
function entity:can_move()
  return not self:is_moving() and self._cooldown:get_remaining_time() == 0
end

-- Check whether the entity is currently being pushed/pulled (boolean)
function entity:is_moving()
  return entity:get_movement() and true or false
end

-- Check whether the entity is being pushed (boolean)
function entity:is_being_pushed()
  local hero = game:get_hero()
  return self:overlaps(hero, "facing") and hero:get_state() == "pushing"
end

-- Check whether the entity is being pulled (boolean)
function entity:is_being_pulled()
  local hero = game:get_hero()
  return self:overlaps(hero, "facing") and hero:get_state() == "pulling"
end

-- Cooldown between pulling/pushing by 1 tile
function entity:start_cooldown()
  self._cooldown:stop()
  self._cooldown = sol.timer.start(self, 500, function() end)
end


It requires some utility functions, found here: https://gitlab.com/voadi/voadi/blob/feb500f3e3325d99dc394398ecbb1c7dc5710b23/data/scripts/utils.lua

Here's the upstream file: https://gitlab.com/voadi/voadi/blob/56118adfe3516a1ea8ce816e4f46422576d6b976/data/entities/mirror.lua
#2
This is a super awesome project using Solarus: http://beckylavender.co.uk/portfolio/the-zelda-dungeon-generator/

And it's heavily documented, making it even better!! http://beckylavender.co.uk/wp-content/uploads/2017/11/ZDG_Dissertation.pdf

I'm surprised we'd never heard from this person before - I'm going to reach out to her.
#3
General discussion / Discord <-> Matrix bridge status
February 16, 2019, 05:17:11 PM
Hey all,

Going forward I'm going to use this thread to update the status of the bridge in case anything happens.

Currently (Feb 16, 2019 @ 11am) the bridge is down. The developer was last seen about 12 hours ago saying:

QuoteI've updated the Telegram and Discord bridges. The Discord bridge looks to be taking a bit longer to actually apply the update though, and might be down for longer than expected.

This is the longest period of time the bridge has been down since we first set it up December 9th. The rooms are still accessible to Matrix users, they just aren't syncing messaging to/from Discord.
#4
General discussion / 4-color CC0 tileset (8px tiles)
February 16, 2019, 04:16:54 AM
This tileset looks very cool: https://stealthix.itch.io/4-colour-tileset

It's the first free 8x8 tileset I've seen that would be a good fit for Solarus. The only problem is that many prebuilt entities need to be 16x16 (including the hero). But you can probably get around this with a bit of hacking in the scripts. It would be interesting to see someone build a tiny game with this.
#5
General discussion / testing 123 please ignore
January 29, 2019, 11:55:41 PM
testing RSS chatbot integration
#6
In the Solarus chat, Clewdrew noted that many channels on the Discord side don't have descriptions. Here's our suggestion for descriptions:

Mine:

  • Solarus - General community chat. Feel free to say hi and ask questions.
  • Solarus Dev - Developing Solarus itself.
  • Solarus Random - Off-topic discussion with the Solarus community.
  • Free Resource Pack - Creating a free resource pack for use with Solarus.
  • Vegan on a Desert Island - Cheeky adventure game about politics and animal liberation (WIP). https://gitlab.com/voadi/voadi

Cluedrew:

  • solarus-fr: Would the french version of the solarus channel description do?
  • new-website: Development of the new website.
  • solarus-[platform]: Porting the Solarus engine to [platform].
#7
My game has several different types of grass and water. Is there a way to set ground1 and ground2 to a different sprite when entering a different area?

I'd also be curious if it's possible to set the direction of the ground based on the hero's direction. eg, when the hero faces left ground2 looks different.

Thanks!
#8
General discussion / Solarus 1.6 snap release
December 28, 2018, 02:36:43 AM
Hi everyone,

I've just put some finishing touches on the Solarus 1.6 snap release.

If you're on Ubuntu, you can search for "Solarus" in the software center and install it from there. That's it!

For other distros, you need to first install snapd. It's probably as simple as sudo <yourPM> install snapd, but you can click the link for more info.

Once you have snapd installed, just run sudo snap install solarus and you'll be good to go!

Known bugs are listed here: https://gitlab.com/solarus-games/solarus-snap/issues

Please report any you find to that. Thanks!

If sound doesn't work: Open Ubuntu Software > Installed > Solarus [ver] > Permissions > Audio Channel and give it permission to run PulseAudio. (thanks latchk3y!)
#9
Development / How do you organize your quest data files?
December 06, 2018, 11:11:09 PM
Here's how badly disorganized I am:



It's becoming an actual blocker for me. I'm afraid to create new maps and experiment because I don't want to worsen to this mess.

I may solve this by writing documentation on how Vegan on a Desert Island's files are organized. But I'm curious if anyone has already done this, or has any insight to offer.

Some parts I'd like to cover:


  • Dialogs - when do you create folders? Do you categorize by area, by character, by time period? How do you organize branching dialog?
  • Maps - same as above.
  • Sprites - when do you create new a sprite sheet? what is the folder structure?
  • Scripts - same as above.
#10
Development / Lua promises
December 03, 2018, 06:24:02 PM
I've been working on animating an intro sequence for my game, and running into callback hell with movements. Eg, one movement must happen, then the next movement happens, then a timer happens, then another movement happens, etc.

I found a few Lua promise libraries:


The top one looks very promising. Anyone have experience with this? Also just wanted to share in case others find it interesting.
#11
Your scripts / "End Credits" script (rolling credits)
November 15, 2018, 09:54:14 PM
I didn't share this before because it'd likely need to be adapted and parts of the code aren't super nice, but I figure maybe this will benefit someone.

It requires a dialog called "end_credits", and it just rolls through it until it's done.
Code ( lua) Select
-- ♡ Copying is an act of love. Please copy and share.

-- Game credits. Plays at the end of the game.

local end_credits = {}


-- https://stackoverflow.com/a/8316375/8811886
function build_array(...)
  local arr = {}
  for v in ... do
    arr[#arr + 1] = v
  end
  return arr
end


-- Called when the menu is started
function end_credits:on_started()
  local lh = 12 -- line height in pixels
  local speed = 24 -- scroll speed in px/s

  -- Credits dialog
  self.dialog = sol.language.get_dialog("end_credits")

  -- Break dialog text into a table of lines
  local lines = self.dialog.text
  lines = lines:gsub("\r\n", "\n"):gsub("\r", "\n")
  lines = build_array(lines:gmatch("([^\n]*)\n"))


  -- Box where the credits go
  self.credits_surface = sol.surface.create(
    160, -- center the surface on the screen
    (#lines * lh) -- surface is large enough to hold all lines
      + 144 -- surface scrolls in from the bottom, so has a padding top equal to the screen size
      + 16 -- room for 8px top and bottom padding
  )

  -- Loop through all dialog lines and draw them
  for i, line in ipairs(lines) do
    local line_surface =  sol.text_surface.create({font="Comicoro", font_size=16, text=line})
    -- Draw the given line
    line_surface:draw(
      self.credits_surface,
      8, -- left padding
      i * lh -- bump it down by line number and line height
        + 8 -- top padding for whole box
    )
  end

  -- Animate the text box upwards
  local m = sol.movement.create("straight")
  m:set_angle(math.pi / 2)
  m:set_speed(speed)
  m:start(self.credits_surface)

  function m:on_position_changed()
    local credits_surface = end_credits.credits_surface
    local x, y = credits_surface:get_xy()
    local w, h = credits_surface:get_size()

    log(string.format("Credits text box: (%d, %d)  ;;  y+h = %d", x, y, y+h))

    if y + h < 0 then
      -- Credits are out of view, end the menu
      log("Credits animation finished.")
      self:stop()
      sol.menu.stop(end_credits)
    end
  end

end


-- Called each frame
function end_credits:on_draw(dst_surface)
  dst_surface:clear()
  self.credits_surface:draw(dst_surface, 48, 144)
end


return end_credits


It's a menu, so put it in a .lua file (eg scripts/menus/end_credits.lua) and use something like this:

Code ( lua) Select

local end_credits = require("scripts/menus/end_credits")

sol.menu.start(game, end_credits)
#12
General discussion / How loud should sounds be?
November 04, 2018, 05:50:11 PM
One thing I don't quite understand, is how loud to make each of my sounds/music so it meets the user's expectation based on the volume they've set on their computer.

For example, sometimes I'll mute my game while working and be listening to other music. Then when I unmute my game, I have to turn the volume down. This is because the music in my game is louder than the other music I'm listening to. The other music is usually an album played with VLC, or a YouTube video.

I'm aware of "the loudness wars." Where did we ever end up with that? Is there any sort of standard for loudness? Ideally, I want users to not have to turn the volume up or down when switching from other software to my game.
#13
There are C++ to ES6 transpilers that seem to work reasonably well, like Emscripten: http://kripken.github.io/emscripten-site/

For example, you can see the open source game FreeDink running in the browser: https://play.freedink.org/ This game is written in C++ and ported to the web with Ecmascripten.

I think Solarus would have to be modified for this to work since it reads the filesystem to load the quest and write save data, but it might be possible.
#14
General discussion / Puzzle design
November 01, 2018, 03:48:52 PM
I've seen some great resources here about creating beautiful maps, but what about designing puzzles? How do you even approach designing something that you yourself should not be able to easily solve?

I've found one great video series, starting here: https://www.youtube.com/watch?v=4YpQVdZOjgI

Curious if others have any more input or resources.
#15
I'm experimenting with making NPCs shiver back and forth when I use my vacuum item on them. It's a pixel movement that jitters an entity left and right by 2px.

When I try it, it says: Error: Illegal direction 3 for sprite 'animals/seagull_trash2' in animation 'walking'

I'm guessing that using a movement on an NPC is "smart" and makes the NPC automatically change direction toward the movement and try to walk that way. Am I right? If so, is there an easy way to override it?

It's not a big deal, and I might just not do this in my game. It doesn't seem documented though, and kinda surprised me.
#16
Development / Solarus could use a UI framework
October 26, 2018, 11:19:04 PM
I've been focusing on menus lately. At first I found them intimidating but I'm starting to get the hang of it now. As a web developer, creating menus by drawing pixels is very weird. I know this is just how games work, though.

Still, I'm tempted to start breaking my menus into reusable components like in Vue.js. For example, I have multiple menus which contain a grid of items the player may highlight and select. I could see having an API like

Code ( lua) Select
-- Example menu with reusable components

local title_bar = require("scripts/ui/components/title_bar")
local item_picker = require("scripts/ui/components/item_picker")

local menu = {
  components = {
    title_bar:new({bg_color={0,0,0}, text="Inventory"),
    item_picker:new({row=3, col=5, gutter=8})
  }
}

function menu:on_draw()
  for _, component in ipairs(self.components) do
    component:draw(self)
  end
end

return menu


idk, I haven't fully thought this through obviously, but I'd love to see some sort of component-based design in action. I haven't fully worked out how this needs to happen in Lua.
#17
Your scripts / NPC item picker script
October 24, 2018, 11:24:07 PM
This is sorta hard to explain, but basically I wanted the ability for any NPC to prompt the hero for an item, then the hero has the option to select an item in their inventory to give. It works like Paper Mario if you've ever played it. Demo:



My game revolves a lot around trading items, so it's important that this is streamlined. It allows writing code like this:

Code ( lua) Select
local bunny = map:get_entity("bunny1")

function bunny:on_interaction()
  game:start_dialog("bunny.want_carrot", function()
    bunny:ask_item(function(item)
      if item and item:get_name() == "carrot"
        item:set_variant(0)
        game:start_dialog("bunny.thanks_for_carrot")
      elseif item then
        -- a non-carrot item is given
        game:start_dialog("bunny.no_thanks")
      else
        -- The action was cancelled (item == nil). Do nothing.
      end
    end)
  end)
end


I'm not sure I organized my code the best way, but here it is:

scripts/menus/picker.lua:
Code ( lua) Select
-- ♡ Copying is an act of love. Please copy and share.

-- This menu ultimately exists to let you call npc:ask_item()
-- It's designed to be called from npc:ask_item() and really cannot stand alone

require("scripts/multi_events")

-- Items that could appear in this menu
local key_items = {
  'trade',
  'whiskey',
}
local player_items = {} -- Items the player actually has


-- Get the XY for an item sprite based on its position
local function get_slot_xy(i)
  local mx, my = 24, 32 -- offset from menu origin

  local r = math.ceil(i/4) -- row of this slot
  local ix = ((i-1)%4)*24
  local iy = (r-1)*24

  local x = mx + ix
  local y = my + iy

  return x, y
end


local picker = {} -- item picker menu


function picker:initialize()
  local font, font_size = "Comicoro", 16

  -- Graphics
  self.menu = sol.surface.create(144, 104)
  self.menu_bg = sol.sprite.create("menus/menu_bg")
  self.item_sprite = sol.sprite.create("entities/items")
  self.cursor_sprite = sol.sprite.create("menus/cursor")

  -- Player sprite
  self.player_sprite = sol.sprite.create("main_heroes/rachel")
  self.player_sprite:set_animation("walking")
  self.player_sprite:set_direction(3)

  -- NPC sprite
  self.npc_sprite = nil

  -- Question text
  self.title = sol.text_surface.create({text_key="menus.picker.question", font=font, font_size=font_size})
  self.title:set_color({0, 0, 0})

  -- Nope button
  self.nope_button = sol.surface.create(32, 16)
  self.nope_button:fill_color({198, 34, 0})
  local button_text = sol.text_surface.create({text_key="menus.picker.nope", font=font, font_size=font_size})
  button_text:draw(self.nope_button, 4, 8)
end


-- Called when the picker menu starts
function picker:on_started()
  sol.main.game:get_hero():freeze()
  sol.main.game._picker_enabled = true

  -- Set items the player has
  player_items = {}
  for i, v in ipairs(key_items) do
    if sol.main.game:has_item(v) then
      table.insert(player_items, v)
    end
  end

  -- Graphics
  self.npc_sprite = sol.sprite.create(self._npc:get_sprite():get_animation_set())
  self.npc_sprite:set_direction(3)

  -- Initialize properties
  self.cursor = 1
  self._selection = nil
end


-- Set/get the selected item (when "action" is pressed)
function picker:set_selection(item)
  self._selection = item
end

function picker:get_selection()
  return self._selection
end


-- Called every frame
function picker:on_draw(dst_surface)

  -- Draw BG graphics
  self.menu:draw(dst_surface, 56, 16)
  self.menu_bg:draw(self.menu)

  -- Draw characters
  local x, y
  x, y = self.player_sprite:get_origin()
  self.player_sprite:draw(self.menu, 24+x, 8+y) -- draw player sprite
  x, y = self.npc_sprite:get_origin()
  self.npc_sprite:draw(self.menu, 104+x, 8+y) -- draw NPC sprite

  -- Draw question
  self.title:draw(self.menu, 49, 16)

  -- Draw items, loop through inventory
  for i, item in ipairs(player_items) do
    if sol.main.game:has_item(item) then
      self.item_sprite:set_animation(item) -- all items are in one sheet
      local x, y = get_slot_xy(i) -- item slot XY
      local ox, oy = self.item_sprite:get_origin() -- origin offset
      self.item_sprite:draw(self.menu, x+ox, y+oy) -- draw item
      if self.cursor == i then
        self.cursor_sprite:draw(self.menu, x, y) -- draw cursor
      end
    end
  end

  -- Draw cancel button
  self.nope_button:draw(self.menu, 56, 80)
  if self.cursor == #player_items+1 then
    self.cursor_sprite:draw(self.menu, 64, 80) -- cancel button cursor
  end
end


-- Called when a button is pressed
function picker:on_command_pressed(command)

  -- D-Pad controls
  if command == "up" then
    self.cursor = self.cursor - 4 -- up and down navigate between rows
  elseif command == "down" then
    self.cursor = self.cursor + 4
  elseif command == "left" then
    self.cursor = self.cursor - 1
  elseif command == "right" then
    self.cursor = self.cursor + 1
  end

  -- Cursor must be between 1 and #player_items+1 (cancel)
  self.cursor = math.max(1, self.cursor)
  self.cursor = math.min(self.cursor, #player_items+1)

  -- Handle selection
  if command == "action" then
    if self.cursor ~= #player_items+1 then
      local item = sol.main.game:get_item(player_items[self.cursor])
      self:set_selection(item)
    end
    sol.menu.stop(self)
    sol.main.game:get_hero():unfreeze()
    sol.main.game._picker_enabled = false
  end

  return true
end


-- Initialize picker when the game starts
local game_meta = sol.main.get_metatable("game")
game_meta:register_event("on_started", function(self)
  picker:initialize()
end)


return picker


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

require("scripts/multi_events")
local picker = require("scripts/menus/picker")


-- Add npc:ask_item(callback)

local npc_meta =  sol.main.get_metatable("npc")

-- Start a picker menu for the NPC
function npc_meta:ask_item(callback)
  picker._npc = self
  sol.menu.start(self:get_map(), picker)
  function picker:on_finished()
    callback(self:get_selection())
    picker._npc = nil
    self.on_finished = nil -- destroy this function between calls
  end
end


-- Initialize game with picker menu

local game_meta = sol.main.get_metatable("game")

-- Check whether a picker is already active
function game_meta:is_picker_enabled()
  return self._picker_enabled
end

-- Initialize game with _picker_enabled attribute
game_meta:register_event("on_started", function(self)
  self._picker_enabled = false
end)


Finally, I've just added the script to features.lua:
Code ( lua) Select
require("scripts/npc_ask_item") -- NPC item picker menu
#18
Development / How do PNG fonts work?
October 24, 2018, 10:03:38 PM
I noticed the docs say that .png fonts are supported. I'm hoping I can get clarity on what this means exactly.


  • Is the PNG font a recognized standard? Eg do multiple programs use the same format
  • Is it just a row of characters in a specific order?
  • Are only monospace fonts supported?
  • Does it have to be 8px?
  • Are there any examples?

Thanks!
#19
Development / Stopping a sound effect
October 21, 2018, 07:17:12 PM
Hi all, wondering if there's any way to do this or any plans to add it? I have an item that should make a continuous sound until stopped.
#20
So, I've been learning Lua. For a while I was using pairs() to loop over an array, but later learned that ipairs() is actually the behavior I want. So I went back and edited my game's scripts, and wound up with this totally incoherent commit message: https://gitlab.com/voadi/voadi/commit/22885ca8e0ba0e1ab68a07422163f56b0b8e1048

I had written "Always use ipairs!! Now that I know the difference", but Bash interpreted my double exclamation points as a bang command. Geez... maybe I'll just write my commit messages into nano from now on instead of using the git commit -m <message> syntax.