Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

Screwtapello

84
Posts
5
Topics
26
Followers
7
Following
A member registered Oct 02, 2017 · View creator page →

Creator of

Recent community posts

It’s heartening to see the transition and font from Screen Melt Transitions being used in a far more wholesome context than the source material. Well done!

Decker cards are 512×342 pixels, but by default (in “unlocked” mode) the top 16 pixels are covered by the menu bar.

When you save deck as “locked”, that menu-bar no longer appears, and you get to see the pixels that were under it instead.

Obviously it’s not great to have pixels that will be visible in the final product that you can’t see while working on it, so if you’re using one of the drawing tools (selection, pencil, lasso, line, etc.) and you press the “m” key, the menu bar will be hidden so you can draw underneath it. You can get the menu back by pressing “m” again, or by switching to the “interact” or “widget” tools - by pressing the F1 or F2 keys, or by clicking on them in the toolbar if you have that visible.

It occurs to me that maybe it should be organised around “where did you get your fonts” rather than “what format are they in”. Although grouping by format makes sense from an implementation point of view, these formats are all niche enough that nobody’s going to say “oh, my favourite font creator published a font in FZX format, how can I use that with Decker?”

To be clear, things like the OFL’s “Reserved Font Name” or the GPL’s “must distribute source code” clause don’t actually do anything to prevent abuse like this. They just give you (in this case, IJ) the legal right to sue people who do break those rules, which means preventing abuse depends on IJ having the time and money to hire lawyers to do the suing, and investigators to figure out who needs to be sued, and possibly more lawyers to figure out which of those people live in countries where international treaties allow them to be sued from whatever country IJ lives in.

Without all that additional stuff, those clauses just amount to saying “don’t be a jerk” in fancier language, which.. I think was already implied.

To get SDL_version.h on regular Fedora, you’d need to sudo dnf install sdl2-compat-devel and that should do it.

Unfortunately, Fedora Atomic is more like Android or iOS, where you can’t just install something into the system, you need to set up a special isolated development environment. I’m afraid that’s not something I’m familiar with. If nobody here has experience with development on Fedora Atomic, you might be able to get more help in a Fedora-specific community.

Decker community · Created a new topic Decker Font Importer

As hinted at previously I’ve been working on a deck that imports pixel fonts from various indie gaming and retrocomputing formats into Decker, for use in your Decker decks.

Supported formats include:

  • Raw ZX Spectrum font data
  • FZX, commonly used with the ZX Spectrum
  • UF1, UF2, and UF3, used with the uxn/varvara virtual console
  • YAFF fonts
  • Glyphs files, as exported from FontStruct

If you’re not familiar with any of those formats, the deck links to sources of fonts in each of those formats for you to explore and play with.

I still have a few items on my to-do list, but it works for the use-cases I originally wanted (getting more classic Macintosh fonts to go alongside Decker’s built-in fonts) and I would like to see what bugs other people encounter and what features they’d like to see.

https://screwtapello.itch.io/decker-font-importer

The basic logic you have there is sound, but I can spot a few problems that are likely to trip you up.

The most basic is, Decker doesn’t have true and false constants - those are just variable names and are nil by default, so if true alert["hello"] end will never show an alert, unless you happen to have defined true:1 somewhere else. Where you would write true and false, just change them to 1 and 0 respectively.

The next problem you’re likely to hit is that the value of the adult_ok variable is not preserved between events. If you want to store that value somewhere, you need to store it in something - in this case, probably a button with the “Checkbox” appearance on a card somewhere. If you don’t already have a card to store the state of the game, you can make a new one called, say “gamestate”, put a button named adult_ok on it, give it the “Checkbox” appearance, and then in your code you can do:

gamestate.widgets.adult_ok.value:alert["Are you OK with seeing adult content?" "bool" "Yes"]

…to ask the user and store the resurt in gamestate.widgets.adult_ok.value. Then you can later check it:

if gamestate.widgets.adult_ok.value
 go["adultcontent"]
else
 go["nextmorning"]
end

Of course, that’s assuming you actually want to store the answer and adjust the game accordingly. If you just want to make a disclaimer and only proceed to the actual game if the player clicks “yes”, then you don’t actually need to store that anywhere: if the player winds up anywhere but the first two cards, you can assume they must have clicked “yes” at some point in the past. Then you can just write:

if alert["Are you OK with seeing adult content?" "bool" "Yes"]
 go["thesagabegins"]
else
 go["titlescreen"]
end

…and never worry about it again.

Thanks!

I don’t know of one off the top of my head. Digging around in Decker’s source-code, I managed to get this list of shortcuts from the code that sets up menus:

Cards...           C
Copy               c
Copy Image         c
Copy Rich Text     r
Copy Sound         c
Copy Table         c
Copy Widgets       c
Cut Image          x
Cut Sound          x
Cut Widgets        x
Cut                x
Darken Image       k
Fullscreen         f
Invert             i
Keycaps...         k
Lighten Image      j
Listener           l
Open...            o
Order...           O
Paste Card         v
Paste Image        v
Paste Inline Image v
Paste Rich Text    v
Paste Sound        v
Paste Table        v
Paste              v
Paste Widgets      v
Prototypes...      T
Query...           u
Quit               q
Redo               Z
Rotate Left        ,
Rotate Right       .
Script...          e
Script...          r
Select All         a
Snap to Grid       p
Sounds...          S
Tight Selection    g
Toggle Comment     /
Undo               z
X-Ray Specs        r

Most of those shortcuts are only available at certain times (for example, ^/ to toggle comments only works in the script editor) and there’s shortcuts that aren’t in that list (like m to toggle the menu while in drawing mode).

It might be a fun graphic-design challenge to make a Decker keyboard shortcut zine with Millie’s zine template.

As far as I can see, ^d is not being used for anything, so if that could be “Go to Deck” (and especially if ^e could be “Go to Card” even in the script editor), that would be very handy.

I figured there’d be a Good Reason (probably browser-based) why read[] works the way it does, I just couldn’t guess what it might be. Apparently file extensions also count as Unique File Type Specifiers, but I do not doubt it’s a mess on mobile browsers. I guess this is just a nice-to-have, since even filename extensions can lie.

As for returning a name, I guess the hacky thing would be to give the Array interface a .name property that’s normally nil but can be assigned any string value, and have the read[] builtin try to set it before returning the result. A binary-data array isn’t necessarily a file, though, and it doesn’t make a whole lot of sense for it to have a .name property otherwise.

Just for the heck of it, I’m expanding my UXN fonts deck into one that can import various font formats into Decker. As I’ve been working on it, I’ve bumped into a few things that annoy me.

I can’t find an easy way to edit the Deck script

I can edit the card’s script with ^e. I can edit a widget’s script by selecting it and hitting ^r (and the shortcut for switching widgets with ^r in the editor and control-clicking the ghost of a widget is very cool). But I don’t know how to quickly get to the deck script, and I occasionally have to flip back and forth between them when I have a card script that calls handlers defined in the deck script.

For some reason, my fingers have decided that ^e should toggle back and forth between the Deck and Card scripts. It doesn’t work, but I keep trying it and being disappointed.

read[] doesn’t filter by extension

If you read["image"] or read["sound"], the file-picker restricts you to selecting filetypes Decker can read, which makes sense. If you write[array[] ".dat"] then Decker writes array data and automatically sets the file-extension to .dat. However, if you read["array" ".dat"], the file-picker doesn’t restrict you to reading .dat files - you get to pick any file on the system, whether you understand it or not.

Some font file formats have a magic number I can check to see whether it’s a format I recognise, but some are just straight binary data and I have to try it and see. It would be more friendly if I could say “this button only imports .ch8 files”.

read[] can’t tell me the filename it opened

Very simple font formats also often don’t have a font name embedded in them; the font name is effectively the filename. But read[] doesn’t tell me the name of the file it opened, I just get Array interface, so I have to import the font as “New Font”, then let the user rename it. That’s not impossible, but it is annoying.

In addition, if the user clicks Cancel in the file-picker, I’d expect it to return nil or some other indicator that things didn’t work out. Instead, it returns a zero-sized array, just like I’d tried to open an empty file. Again, not a deal-breaker, but “You cancelled, or chose an empty file” is not the most helpful error message.

I don’t believe so, no. Even though it looks a bit like Lua or Python, Lil’s functional-language heritage means there’s no shortcuts, no return or break or continue. This can make some things more difficult, but once you get used to thinking in that style you’re writing much more vectorisation-friendly code, and Lil’s vector operations are much more efficient than regular loops.

Out of curiosity, why can’t Web Decker use “secure context” APIs? Is it just because they might not always be available? So far as I can tell, “secure context” includes https:// URLs (like Itch or Neocities), file:// URLs (a page saved locally to use offline) and http://localhost URLs (if you’re developing a website and testing it locally before uploading it). That’s not everywhere, but I imagine it’s most of the places people would try to use Decker.

Thanks for the tip! I did indeed move to generating the patterns at startup, rather than on the first frame of each transition, and that helped.

I also optimised the inner drawing loop from:

each pos in xs join ys
 # grab the strip from the old screen, starting at the top
 strip:a.copy[pos[0],0 stripwidth,stripheight]
 # Draw the strip at the target position
 c.paste[strip pos]
end

…down to this:

stripsize:stripwidth,stripheight
topfactor:1,0 # pos*topfactor = pos[0],0
paste:c.paste
copy:a.copy

each pos in xs join ys
 paste[copy[pos*topfactor stripsize] pos]
end

For 256 strips, that brought the total op count from ~6000 down to ~4000, so I could paint 512 strips and stay within the operation quota for a transition frame. Of course, then I start pushing against lower-level performance limits… but I’m happy to stop there.

This breaks for me too, on Linux. I think this is a bug introduced with the recent %%IMG3 changes; I’ve reported it on GitHub.

The tip about “prefer pre-shuffled lookup tables to actually calling random[]” is a good one. For the “Screen Melt Transitions” deck (thanks for the shout-out!) I tried to generate random numbers at the beginning of each transition, and as a result I had to lower the transition quality to fit within Decker’s calculation limits.

I do like the way that the transition looks a little different each time you see it, but maybe I should try randomly generating numbers at module-initialisation time rather than on the first frame of the transition. I could even add a reseed[] method to call outside the transition effect, to generate a new pattern for the next transition.

(1 edit)

A field widget stores some text, a slider widget stores a number, a canvas widget stores a picture, that’s about all there is to it.

If you see a canvas widget’s content changing between different frames of animation, then either the canvas is being redrawn by code (as in the Canvases card in the built-in tutorial deck), or there’s some other card or canvas somewhere that has all the different frames, which are being copied into the canvas to create animation.

Decker comes with an example deck called Puppeteer, which includes a module you can include in your own decks to do this kind of animation.

Seconding Millie’s recommendation of “landscape” and “fullscreen” modes.

Decker’s default deck size is very much landscape-oriented, so it’s never going to work very well on a portrait-mode device.

In addition, Decker tries to display itself at an exact multiple of the deck size, to keep those pixels razor-sharp for the retro aesthetic. However, in full-screen mode Decker abandons that restriction and displays itself as large as will fit. For example, an iPhone 16’s screen is 1179px on the short axis, which is ~3.4 times larger than a Decker deck. Normally Decker will scale itself 3x, leaving a wide black margin (about 13% of the screen height, total) around the edges. In fullscreen mode (when you pick “Fullscreen” from the Decker menu), it expands to cover the full height, giving you a bit more space to poke at buttons and drag sliders around.

Here’s a more automated way to clobber the menu font with a custom one.

  1. Copy this text to the clipboard:

    %%WGT0{"w":[{"name":"clobberfont","type":"button","size":[112,16],"pos":[208,64],"script":"on click do\n s:me.font\n t:deck.fonts.menu\n \n t.size:s.size\n t.space:s.space\n each i in range 256\n  t[i]:s[i]\n end\nend","font":"body","text":"Clobber menu font"}],"d":{}}
    
  2. Open a deck with a font you like

  3. Go into Widget-editing mode (Tool → Widgets)

  4. Paste the widget (Edit → Paste Widgets)

  5. This should give you a button like this:

    image.png

  6. Set the button’s font to be the font that you want to use as the menu font (Widgets → Font…)

  7. You now have a button that will clobber the menu font of the deck it’s in, with a font of your choosing

To set the font of a deck:

  1. Copy your customised button to the clipboard (Edit → Copy Widgets)
  2. Open the deck whose menu font you want to change
  3. Go into Widget-editing mode (Tool → Widgets)
  4. Paste your customised button into the deck (Edit → Paste Widgets)
  5. Go into Interact mode (Tool → Interact)
  6. Click the button
  7. You should see the font in Decker’s menu-bar change
  8. Now this deck has a custom menu font, you can delete the button and save the deck, and the change will persist

Warning: Unlike patterns and palettes, Decker does not have an easy way to reset a font to its default appearance. If you decide you want the old font back again, that’s going to be trickier.

Also note that Decker’s interface does not adapt well to fonts that are larger than the default. It does its best, but there are limits:

image.png

If you just want the “next page” button to appear after you click some other button, you can make the “next page” button be “Show None” (in Widget mode, select the button, then from the Widget menu, pick “Show None”), and have the other button make the “next page” button solid:

on click do
 nextpage.show:"solid"
end

Of course, once you test that button, you’ll have to go back and hide the “next page” button again. It might be worth having your deck begin with a “start game” button that specifically goes through and resets all these things before the game begins, so you don’t personally have to remember to do it each time you save the deck.

Super Star Wars would be a good example, I think - the SNES provides an easy way to scale and rotate an image like Decker’s .scale[] and .rotate[] methods. The Star Wars logo in that video looks very like the one in your deck.

For the text crawl, the SNES has an advantage: rather than scaling the whole image in one go, it can change the position and scale factor on each scanline, so the top scanlines are scaled very small, and the lower scanlines are at full size, or even scaled up.

Actually calculating which scanlines to show and the scale to show them at could be quite expensive, but luckily the perspective doesn’t change, so you could calculate them once at the beginning of the scene, or even just calculate them with a pencil and paper and type them in.

I’m imagining something like, for each line of the target canvas:

  • use an easing function on the Y coordinate of the target to pick a Y coordinate of the source image
  • use .copy[0,y width,1] to grab the source scanline
  • use a (different?) easing function on the Y coordinate to pick a horizontal scaling factor
  • use .scale[factor,1] to scale it horizontally
  • optionally, use the chosen scale factor to .map[] white pixels to one of Decker’s shaded patterns (or if you’re feeling fancy, colours in the greyscale ramp)
  • draw it to the target

I’m sure there’s more mathematically rigorous ways to get a proper perspective effect, but I bet that would look pretty good. The question is, would it run at 60fps?

Thanks for shouting-out my Deck Jam entry!

I also really liked the Star Wars like intro animation. For a brief moment I was worried that you’d figured out how to do the perspective-correct text crawl in Decker, but I’m glad to see you’re not that crazy. :)

Reading about Lil I thought that the “graph” of pointers inside the program was very simple because everything is a value and everything is copied. However, if it is possible to store hidden information inside the function I might have not understood something. Is this normal behavior ?

Yes, this is normal behaviour - like a lot of functional languages, Lil has closures. The Lil docs say:

Lil uses lexical scope: variables will resolve to the closest nested binding available, and the local variables of a caller to a function will not be visible or modified by the callee (unless the callee’s definition is nested in the caller)…

Furthermore, functions close over variables in their lexical scope, allowing for encapsulated “objects” with their own mutable state

It even gives an example of putting x : x + 1 inside a nested function so you can make a counter, as you did in your second example.

In the “Events” section of the Decker documentation, one of the listed events is view for any widget, described as “The surrounding card is active, repeatedly at 60hz.” I expected this to be something like “The surrounding card is active and the widget’s animated property is true, repeatedly at 60hz.”

Have I missed something? Is there some way to get a 60hz event without setting the animated property?

The Canvas is the only widget that has both click and release events, and the Button is the only widget that responds to keyboard presses, so you can’t make something that starts when you hold a key and stops when you release.

However, you can make the button send a click, then wait a while, then send a release, which is probably as close as you’re going to get:

on click do
 canvas1.event["click" 0,0]
 sleep[15] # in 60ths of a second
 canvas1.event["release" 0,0]
end

That can look a little weird, since Decker disables all interactive widgets (drawing them greyed out) until the sleep time is complete, but it prevents the player from doing more things while the previous things are still going on.

If you don’t want to freeze things like that, you can build something a little more complex that lets Decker keep doing things while you wait. Create a field to hold the timer countdown, and change the button click handler to:

on click do
 canvas1.event["click"]
 field1.data:15
 field1.animated:1
end

So when the button is clicked, it sends the click event to the Canvas, it loads 15 into the field, and sets the field to be animated (so Decker sends it view events 60 times a second). The field’s view handler looks like this:

on view do
 me.data:me.data - 1
 if me.data = 0
  canvas1.event["release" 0,0]
  me.animated:0
 end
end

So every time view is called (that is, 60 times a second), it subtracts 1 from the value it stores. When it reaches 0, it sends the release event to the canvas, and turns off animation again. As a result, when the button is clicked, the canvas is “clicked”, then things keep happening as usual while the timer ticks down, and when it does the canvas is “released”.

The field can be made “Show None” if you don’t want the player to see it; if you make the button “Show None” then it won’t react to the keyboard. However, you can make the button use the “Invisible” style to hide it, and “Show Transparent” to make it invisible even when clicked. You can also just move it off-screen.

When you click a link in a field, that sends a link event to the field. If the field doesn’t handle it, it bubbles up to the card the field is on - in this case, the contraption’s prototype. If the prototype doesn’t handle the event, it bubbles up to the default event handlers, including this one:

on link x do
 go[x]
end

If you want clicking a link to anything other than go straight to the target, you have to add your own on link... handler to interrupt the default handling.

The easiest thing is just to put it into the prototype’s script, especially if you always want clicking a link in one of these text fields to display an alert before moving on, something like:

on link target do
 alert["You have clicked a link. This will be reported to the authorities."]
 go[target]
end

If you want different contraptions to have different behaviour, you’ll want the link event inside the prototype to become a link event on the contraption, by putting something like this in the prototype’s script:

on link target do
 # forward the event to the contraption
 card.event["link" target]
end

Then you can put different on link... handlers on different copies of the contraption, for different behaviour.

You can’t put an active widget into a rich text field, but you can put a link into a rich text field, and there’s a link event that fires when the user clicks on a link, so that would probably be the easiest way to deal with it.

If you really need a button, you could put a few blank lines at the end of the rich text field, put an extra button inside the contraption, and have an event handler that continually adjusts the position of the button based on the field’s .scroll property. You’ll probably also need to use the maxscroll helper function from that post to figure out whether you’re within (button height) pixels of the end of the content. You may also need to mess with widget ordering to make sure the button appears above the rich text field but behind the horizontal scroll bar.

For the second question, when you say “Font Importer”, do you mean the deck described in this post? For the font you describe, you’ll need the deck as described in the OP, and the changes to support sizes other than 8x8, and the changes to support modern versions of Decker. You should also have your font as black text on a white background - if you want it to appear in white, you can use Decker’s text formatting tools later. Lastly, importing a font adds it to the list of fonts embedded in that deck - to use it anywhere else, save the deck to a file, then open the deck where you want to use the font in Decker, drag-and-drop the deck-with-the-font onto Decker, which should bring up the Font/DA Mover where you can copy it across.

(2 edits)

You left Decker running for 24.8 days?

EDIT: Wait, no, I see what’s going on.

Native Decker computes sys.ms as a float, Web Decker takes a float from the browser and truncates it into a 32-bit integer.

I guess it shouldn’t matter if you’re using sys.ms for performance timing (duration:sys.ms - start) but it does make problems if you’re trying to divide the clock with a modulus operator.

This was my first jam - it’s been really interesting playing with all the submissions, and encouraging to get such lovely comments on mine.

Thanks to everyone who participated!

This was lovely. Very simple, but it’s fun to explore a new place, and this definitely gave me nostalgia for Myst and games like it.

I like the palette! Very moody and retrospective.

This has been my first ever jam of any kind, and I was thinking about doing more of them, so reading about your experiences on card10 was interesting, and has given me something to think about. Thanks!

This is pretty cool! Obviously Decker is not designed for this kind of thing, but that only makes it more cool. :)

I couldn’t get the metronome or the one-voice synthesiser to work, but the tutorial cards and mouse-tracking and even the tone generator worked fine.

As a proof-of-concept, I wasn’t expecting much beyond the addition example on the first card, but you clearly went a lot further than that! Well done!

This is very cute! The sound effects help a lot - are the barks also courtesy of Buddy the Dog?

You’re right, that was pretty dark, but in a dark-humour kind of way. The name/gender screens in the introduction were a fun way to introduce the subject matter, and all the pictures of Deo were really well drawn.

I played enough times to get a picture with Deo, and now I feel like I’m an accomplice to some kind of crime.

I learned about frogs, chickens, and the Philippines today! Thank you! And your drawings are extremely cute.

Those feelings are a bit outside my experience, but you express them well. The crumpled blank paper aesthetic makes me feel isolation and emptiness, which I think is what you were going for.

That’s some fun abuse of Decker widgets. I was particularly impressed by the sliders-as-toggles on the last card!

I think on loop is checked when you visit a new card, so that the new card has a chance to stop the loop, or start a new one. For example, if one card represents the player standing beside a waterfall, it would be more atmospheric if a loop of waterfall noise played whenever that card was visible. When the player visits that card, the loop should start immediately (even if some other loop was already part-way through playing) and when the player leaves that card, the loop should stop immediately (even if the waterfall noise was part-way through). Calling on loop on card transitions makes that kind of effect possible.

If your two songs are less than 10 seconds in total, you could combine them into one sound and loop that quite easily.

If your two songs are more than 10 seconds long in total, you might add a “last loop started at” field on the same card as the counter that switches between them. When you start playing a sound, update the counter, and set the “last loop started at” field to sys.ms (the current time in milliseconds). When on loop is called, it passes in the old sound - if the “last loop started at” time, plus the duration of the old sound in milliseconds (old.duration*1000, since the duration is in seconds), is nearly equal to sys.ms, then we’re probably looping because we got to the end of the sound. If the start time + the duration is greater than sys.ms, then the loop has been called early, probably because of a card change. If you return the original sound, it will continue playing it, rather than restarting it.

This is pretty handy!

As a tip, since the YouTube URL is always based on the base URL, the ID, and the timestamp, instead of creating it at insert time and adding it as an extra column, you can create it when the user clicks on the grid. That way, you get extra space on-screen for the description column, and if you edit the timestamp, you don’t also have to edit the URL to make it “stick”.

Also, that is a very cool video to use as the default example. :)