oakes / play-clj

A Clojure game library
The Unlicense
940 stars 72 forks source link

Rendering a label doesn't work unless the screen function evaluates to it #92

Closed MasterMastic closed 8 years ago

MasterMastic commented 8 years ago

I've asked this on SO over here and I think this is an issue, so I'll just copy-paste here:

I'm trying to render! a simple label on the on-render screen function, but it doesn't work. All I get is a blank screen.

It took me some time but I finally found that this happens when I return something else but the label. For example, this works:

(defscreen main-screen
  :on-show
  (fn [screen entities]
    (update! screen :renderer (stage))
    [])

  :on-render
  (fn [screen _]
    (clear!)
    (render! screen [(label "Hello, world!" (color :white))])))

This doesn't (all that's changed is the last line on on-render):

(defscreen main-screen
  :on-show
  (fn [screen entities]
    (update! screen :renderer (stage))
    [])

  :on-render
  (fn [screen _]
    (clear!)
    (render! screen [(label "Hello, world!" (color :white))])
    ["I just wanna pass my own data here, ugh.."]))

For testing, I replaced the label with texture and it did work.

What's causing this? it seems so arbitrary (the label I evaluate to in the first example gets completely ignored anyway! so why do I have to pass it? I don't get what's going on).

P.S. creating the label on every frame is intentional in this case, and I know I could just set it from one I make in on-show, but I tried to be a bit more dynamic. I also tried to put a Thread/sleep after render! to make sure I have a visible time-window between rendering & clearing.


Is this an issue or am I missing something fundamental?

oakes commented 8 years ago

Labels and other play-clj.ui entities need to be added to the stage in order to be rendered. This is done automatically after any screen function is run, but only if you actually return the entities. Since you are not returning the label entity, it never has a chance to get added to the stage. The reason this doesn't happen with texture entities is that they use a completely different part of libGDX and doesn't require being added to the stage. Is there a reason you don't want to return your entities at the end of the function? That's what the return value is designed for.

MasterMastic commented 8 years ago

Thank you @oakes , I'll give it a shot as soon as possible and update. If I understand you correctly I could just conj any UI entity I make to the entities vector and according to my example above I could ignore it completely as-well, right? To answer your question: I had in mind that the entities vector are for passing any state I would need, so I really don't see why would I want to pass a label object when a label object isn't part of my state. And by state, I mean my actual game data, and not any rendering objects such as UI entities or even anything "technical" (if that makes sense). So e.g. if I have a string I get by (let [state (first entities)] (-> state :player :name)), and I may wanna draw it and may not, having this kind of design unnecessarily complicates my logic, quite severely imo, especially if the number of labels aren't fixed. Compare simply drawing the label on the frame, to, as you say: cue it up (insert to stage), possibly & probably create a mapping from the game data it originates from to it, and draw it at.. well I'm not even sure if I ever would because I might not call it on the next frame (contrived example: make a text blink, and with multiple label objects, a fixed amount -- especially one -- is certainly common but an unfixed amount is also very very common).

If this is by design (well, I assume this is an accepted limitation imposed by libgdx), could I workaround this somehow? I don't know libgdx but maybe could I mutate the stage in-place with an insertion? I'd even go that far (I really don't want to complicate my logic just because I want to render text). And maybe this could be abstracted by play-clj in the future into a text rendering function (that would work like textures and other non-UI entities)?

Thanks again!

oakes commented 8 years ago

The entities vector is only really meant to hold entities. If you just want to store arbitrary data, I would suggest putting it in the screen map using update!. It's not just for your renderer or camera; for example, in dungeon crawler, I store a custom cursor there.

There are certainly times when you may want to temporarily remove entities. You can do that by simply removing them from the entities vector. You can always temporarily store them in the screen map so you can add them back to the entities vector later. If this system proves inflexible, you can also avoid the render! function entirely and create your own loop in which you draw the entities yourself.

MasterMastic commented 8 years ago

Okay, it seems the methodology is different than the one I know so I'll really dive into the examples. I'd love to see a guide of how the entities are idiomatically managed and related to game data, by the way. Thank you!