Jach's personal blog

(Largely containing a mind-dump to myselves: past, present, and future)
Current favorite quote: "Supposedly smart people are weirdly ignorant of Bayes' Rule." William B Vogt, 2010

An hour fighting with play-clj

I spent an hour tonight playing with play-clj, with the end goal being a Pong clone. Unfortunately I distracted myself with how play-clj works in a way I don't like, and got nowhere.

I got started game programming with PyGame, which is a Python wrapper on top of SDL with some extra goodies. I think I've been spoiled by the clean mental model of 2D games that SDL provides, along with the total control over the main loop. But PyGame isn't a game engine, it's not a game "framework". It doesn't give you all that much. I enjoy that level of flexibility, though, and the necessary tradeoffs in what I worry about involved in using a game framework always seem to irritate me. One day I hope I'll find a really nice one, that's nice for both 2D and 3D, and has good Clojure support. Unfortunately, play-clj is not it, for me.

What follows is just my personal notes from when I started to when I stopped. I added/cleaned up a couple things in this after-edit. There's a macro, but that's about it in terms of code. I've got nothing against play-clj after this exercise, I'm sure it's fine if you can tolerate the way it and LibGDX do things. Wonderful even. But it's not for me. (At least play-clj. Potentially someone could write a nice enough wrapper around LibGDX that I'd want it.)

I read through the getting started tutorial a few nights ago. My most important thoughts after completing it:

0,0 in the bottom left is weird. I'm used to it being the top-left. It probably makes certain math simpler though...

Textures aren't associated with a default x, y. This bit me when I was following along and was perfectly fine leaving my texture at 0,0, so in the :on-show function I didn't bother 'assoc'ing an x and y, and so in my :on-keydown function I got a null error about trying to 'inc nil.

Game entities *array* where order matters and things aren't located by name is very weird. Solution is to just make the first item a map, and macro the plain fns taking an entities as second argument to auto-destruct.

:on-key-down, no :on-keys-pressed. Must used (key-pressed?) in render loop. This reveals the leaky abstraction that is event reactions over game programming... If I want to hold down the down arrow and the right arrow at the same time, to move diagonally, I can't with only :on-key-down.

Anyway, let's make Pong!

Step 1: Create assets. One ball, one rectangle. How big? Let's go for 50x50 ball. We'll save 1 with a white background to test if we can load a texture with a specific color being transparent, and we'll save the 2nd as a transparent png to begin with. We'll go for a paddle that's the same width, but twice the height. We can always scale things later.

Let's write a function to return these.

(defn load-textures []
{:ball (texture "ball_white_bg.png")
:paddle-left (texture "paddle.png")
:paddle-right (texture "paddle.png")

I know I should put the }) on the last line. Sue me.

I tried calling this in the REPL. "No OpenGL context found in the current thread." Well that sucks. I guess I can only call the texture macro when inside a defscreen. Actually I should have known this wouldn't have worked, because in the tutorial I tried '(def myText (texture "..")) which failed. Add that to the list of smells I don't like.

I want to know: is it memoized underneath? Or do I have two paddle pixmaps in memory? One nice thing about PyGame is I know upfront, because you create a generic surface that is independent of its position or other attributes and come render time can blit it multiple times if you want. Thus you only need one piece of memory for the bitmap. Are textures like surfaces? But then why does play-clj give me a TextureRegion?

At this point I wish to know if I can, for the ball, set white to be the same as transparent. In PyGame I just set the colorkey of the surface. Looking at the texture code, I don't see anything for this, nor in the underlying TextureRegion. Some Googling suggests it might be in the underlying PixMap.. but how do I get at it? I look through some docs and decide it's not worth it. I'll just make sure my assets already contain the transparency info. Hopefully when I run this thing it'll work... (Found out later, it does. Yay!)

Time to set up a main-screen. But first, I play with a macro that will make working with the event handling functions play-clj forces on me a lot nicer. Namely, I intend to stuff all my entities into an array of size 1, containing a map of my entities. I want the input 'entities' argument to already have done this destructuring for me and just be the plain map I want, and I want any non-nil output of these handle functions to be 'merge'd into the map and returned to update the real entity vector.

(defmacro handle [args & body]
(let [screen (gensym)
entities (gensym)]
`(fn [~screen ~entities]
(let [~(first args) ~screen
~(second args) (first ~entities)]
(if-let [~'res (do ~@body)]
(merge ~(second args) ~'res))))))

Is there a better way to write this macro? Probably.

One downside is that some play-clj functions expect the entities list.. I get an error when render! tries to call draw!.

On the plus side, here's a better way to write that macro.

(defmacro handle [args & body]
`(fn [screen# entities#]
(let [~(first args) screen#
~(second args) (first entities#)]
(if-let [~'res (do ~@body)]
(merge ~(second args) ~'res)))))

Could maybe even be improved by changing ~'res to res#, losing one character.

I can't get it to work like I think it should. I remember reading somewhere that it should "just work" if I store my entities in a single map, but it's not working. At this point, I'm giving up.

I know, I know, I should just discard my idea of doing things the way I want. If I just accepted the Way I probably could have spent the last hour getting somewhere with a Pong-clone. But this is Clojure. I don't have to accept someone else's Way if I don't want to, I can make my own Way with macros and blackjack! Unfortunately I don't feel like macroing the crap out of play-clj.. Maybe the underlying libGDX, but honestly, I think I'd have better luck going back to my side-project of porting something similar to PyGame's API on top of LWJGL...

Posted on 2015-05-06 by Jach

Tags: clojure, programming


Trackback URL:

Back to the top

Catling June 12, 2015 04:54:11 PM Well, I'm not really that programming person, but good luck making the game!
Back to the first comment

Comment using the form below

(Only if you want to be notified of further responses, never displayed.)

Your Comment:

LaTeX allowed in comments, use $$\$\$...\$\$$$ to wrap inline and $$[math]...[/math]$$ to wrap blocks.