Kotlin 117
Creeping up on actually having to display some objects. Maybe I should just do it today?
Let’s see. Where are we? Our Flyers object knows how to produce pairs satisfying a predicate, and to perform a function on every element. It doesn’t have much of an idea what kind of object are in it: we could just about create it as generic.
The Game object knows how to update, given deltaTime
: It just tells all the flyers to update:
fun update(deltaTime: Double) = flyers.forEach { it.update(deltaTime)}
We can tell it how to draw as well, roughly like that.
Our FlyingObjects, however, do not know how to draw. Let’s rig something up. It seems to me that the spirit of Kotlin would be to give each kind of FlyingObject, as we create it, a function to draw itself. I think we should make that function draw the object in some canonical form, probably “x pointing east”, which should, with any luck, correspond to a heading of zero. The drawing should be centered around zero coordinates.
What about scaling to the screen size, translating to the real location, and so on. That, too, is a drawing function. For now, we’ll leave it that what has to happen is that all the objects update their position and status (disappearing or appearing as appropriate), and then we draw them all. As each one draws, it will assume that scaling is already done: it can be done once and for all at the beginning of the draw cycle. Everyone is drawn at the same Universal scale. The object knows its position, so will translate to that position. It knows its heading, so it will rotate to that heading. then it will draw itself.
I have the ship drawing code in Lua, from my Codea version of this program:
function Ship:drawLineShip()
line(-3,-2, -3,2)
line(-3,2, -5,4)
line(-5,4, 7,0)
line(7,0, -5,-4)
line(-5,-4,-3,-2)
accel = (accel+1)%3
if U.button.go and accel == 0 then
strokeWidth(1.5)
line(-3,-2, -7,0)
line(-7,0, -3,2)
end
end
It looks like that, when I draw it on a card. The little triangle at the tail represents thrust, the rockets firing to accelerate the ship. We’ll want to look to see how that accel
flag is handled .. or just work out our own scheme.
What scale should we use in our drawing for our Kotlin screen, which is 1000x1000 pixels? On my 10.something inch wide iPad, the ship is about 0.2 inches end to end. The iPad screen is 1366 pixels wide, for obvious reasons which escape me at the moment. So 14 pixels per inch … he’s drawn at scale 2, I’d guess. So double all those coordinates. And our universe is about 10 that size, so let’s draw our Kotlin guy at 20x scale compared to the Lua one. That should get us about right
OPENRNDR has a polyline capability, I believe. Yes. It calls them lineStrip for reasons known only to the developers. There’s a lot of mystery out there today. I’m going to just type in what I think OPENRNDR would like me to say to draw my ship. And, sure, why not let the computer do the math.
I write this “test”:
@Test
fun `lines scale`() {
val lines = listOf(
Vector2(-3.0, -2.0),
Vector2(-3.0, 2.0),
Vector2(-5.0, 4.0),
Vector2(7.0, 0.0),
Vector2(-5.0, -4.0),
Vector2(-3.0, -2.0)
)
lines.forEach {println(it*20.0)}
}
Its output is:
Vector2(x=-60.0, y=-40.0)
Vector2(x=-60.0, y=40.0)
Vector2(x=-100.0, y=80.0)
Vector2(x=140.0, y=0.0)
Vector2(x=-100.0, y=-80.0)
Vector2(x=-60.0, y=-40.0)
I paste that into my new drawing function …
fun drawShip(drawer: Drawer) {
val points = listOf(
Vector2(-60.0, -40.0),
Vector2(-60.0, 40.0),
Vector2(-100.0, 80.0),
Vector2(140.0, 0.0),
Vector2(-100.0, -80.0),
Vector2(-60.0, -40.0)
)
drawer.lineStrip(points)
}
The above was done with very little manual work on my part, other than inserting a few commas, and an initial multi-cursor tour de force where I typed all the “.0” needed to satisfy Kotlin’s fear of converting small Ints to Doubles.
OK, here’s where we see the mind begin to break down. I do have a small “application” that came with OPENRNDR, which I have slightly modified:
program {
val image = loadImage("data/images/pm5544.png")
val font = loadFont("data/fonts/default.otf", 64.0)
val ship = FlyingObject.ship(Vector2.ZERO )
ship.velocity = Vector2(1200.0, 600.0)
var lasttime: Double = 0.0
var deltaTime: Double = 0.0
extend {
deltaTime = seconds - lasttime
val worldScale = width/10000.0
drawer.scale(worldScale, worldScale)
drawer.drawStyle.colorMatrix = tint(ColorRGBa.WHITE.shade(0.2))
drawer.image(image)
ship.cycle(drawer, seconds, deltaTime)
lasttime = seconds
}
}
This program was last used to show a rectangle drifting down the screen, so that I could watch it wrap around. I’ve been working without checking the screen since then. Once I draw this ship … it’ll be tempted to waste time watching the game run. We’ll see what happens. I think I can patch ship to do its thing:
With this code:
private fun draw(drawer: Drawer) {
val center = Vector2(drawer.width/2.0, drawer.height/2.0)
drawer.fill = ColorRGBa.MEDIUM_SLATE_BLUE
drawer.translate(position)
drawShip(drawer)
// drawer.rectangle(-killRadius /2.0,-killRadius /2.0, killRadius, killRadius)
}
fun drawShip(drawer: Drawer) {
val points = listOf(
Vector2(-60.0, -40.0),
Vector2(-60.0, 40.0),
Vector2(-100.0, 80.0),
Vector2(140.0, 0.0),
Vector2(-100.0, -80.0),
Vector2(-60.0, -40.0)
)
drawer.stroke = ColorRGBa.WHITE
drawer.strokeWeight = 4.0
drawer.lineStrip(points)
}
I get a small ship flying across the screen:
I think I am seeing some flicker around the vertical part of the ship’s tail. I fiddle around a bit and it does seem to be there. Might be a scaling thing, changing the line stroke width a bit or something. I fiddle with the draw — this is the danger with having something visible — and this code:
fun drawShip(drawer: Drawer) {
val points = listOf(
Vector2(-60.0, -40.0),
Vector2(-60.0, 40.0),
Vector2(-100.0, 80.0),
Vector2(140.0, 0.0),
Vector2(-100.0, -80.0),
Vector2(-60.0, -40.0)
)
drawer.scale(2.0, 2.0)
drawer.rotate(30.0)
drawer.stroke = ColorRGBa.WHITE
drawer.strokeWeight = 6.0
drawer.lineStrip(points)
}
Gives me this picture:
I’d best think about all this.
Reflection
What have we learned? Well to my eyes, if we retain this screen size, the ship is a bit large. Scale 1.5 seems better. (Yes, I did try it, this vision thing is addictive.)
When I ran the game on the iPad I realized that it ran full screen, i.e. 1366x1024, not square. We should probably provide for that in our current game as well. It may become interesting scaling the ship to the screen size. I did a poor job of that with the Lua version, really just tuning a bunch of numbers by hand.
What about the ship’s vector description? I didn’t feel that I wanted to scale the vectors on every call, but there’s really no harm done. OK, see what I mean about seeing the result? I’m going to change the drawing back to the original size.
fun drawShip(drawer: Drawer) {
val points = listOf(
Vector2(-3.0, -2.0),
Vector2(-3.0, 2.0),
Vector2(-5.0, 4.0),
Vector2(7.0, 0.0),
Vector2(-5.0, -4.0),
Vector2(-3.0, -2.0)
)
drawer.scale(30.0, 30.0)
drawer.rotate(30.0)
drawer.stroke = ColorRGBa.WHITE
drawer.strokeWeight = 8.0/30.0
drawer.lineStrip(points)
}
The new scale is 30, because we had scaled the vectors by 20 in the “test” and 1.5 here. If I left the strokeWidth at 6, the ship was draw with very fat lines. We have to scale the strokeWidth down by the upscale. What if we set it first? Would that work right? (Here I go into the looking at the screen again.) No, its still scaled up. We’ll leave it as above.
Where were we? Oh, right, reflecting.
We now have a decent sense that, given the smaller pixels on my Mac screen, etc, the right scaling from Lua to Kotlin versions is about 30 (pixels to universal measure). I should note that the motion across the screen was about right: it takes about six seconds to cross the screen, which looks close to a good game speed. We’ll absolutely want to tweak that. In the example I’ve been running:
ship.velocity = Vector2(1200.0, 600.0)
My One Mississippi count is slow. Actual time across is about 8 seconds, which is about 10000/1200. I think I worked that out once before. So a maximum speed will want to be somewhere in the range of 1000-2000. Tuning.
I think I’ve learned what I must have hoped to learn about this, which is that I can draw the little line ship just fine, that my scaling numbers make some sense, and that connecting a drawing function into a FlyingObject shouldn’t be terribly difficult. My rough plan for that is that a FlyingObject will have a new member variable drawMe
or something, which will be a lambda that does the drawing. Whether we’ll write it out longhand in the lambda, or call a function that exists on FlyingObject, I’m not sure but I think it should be made separate, so that we can create different drawings for different objects, without editing the FlyingObject class. Yeah, that’s probably best.
One small learning: using screen rotate to turn the ship, angles increase clockwise. I turned the ship 30 from east and it is pointing 30 degrees downward. I’m not sure if I’m supposed to be surprised about this fact. It’s just a fact, to be kept in mind as we work.
Summary
The big win here, on the scale of small steps, which rarely give us anything BIG big, is that except for issues of visual appearance, the program’s function has been built, so far, essentially entirely with tests and code, rather than code / run / look at screen / tweak something. I think that is a good thing because in the past, it has seemed that while I did proceed in fairly small steps, my feedback loop included starting the program and watching it run on the screen. This is a much tighter loop.
I must go sit on the kitchen table now. The cat does it when she wants food. Maybe it’ll work for me.
See you next time!