A Quick Fix for Fuzzy SKShapeNode Lines

February 16, 2015

I’ve been using Apple’s SpriteKit for the train game. It’s gone fairly well, except there have been a few unpleasant surprises with SKShapeNode, which strokes/fills a bezier path. The internet is filled with folks complaining about buggy behavior from it and I’ve definitely had more than a few moments where I daydreamed about dusting off my OpenGL chops.

This is an A/B screenshot of an issue I was running into. At left is before, right is after:

These are SKShapeNodes with paths for the track segments, which are 2pt wide, stroked, and, most importantly scaled up several times (zoomed in on the map). The issue of course is the blurry/fuzzy line rendering.

The ‘fix’ was startlingly simple: setting antialiased = false makes your blurry lines crystal clear when scaled up. This isn’t at all what I was expecting (antialiasing off == stair-stepping, pixelated lines in my mind).

My guess is that this is an optimization. For SKShapeNode, antialiased means it will be drawn on a texture at unscaled resolution and that texture is then scaled as needed. With antialiased off, the curve drawing occurs at the current resolution. This is only reasoning off of the visual results. I don’t notice any significant difference in CPU overhead when antialiased is off.

It’s also curious because SpriteKit seems to perpetually re-render SKShapeNodes whether they have changed or not. At some point I will likely end up writing code to render them to textures, or just moving over to OpenGL and rendering the tracks myself.

At this point in development the visuals are secondary, however. I ran an experiment with SKShapeNode’s strokeTexture and aside from some strange rendering artifacts (texture scaled unevenly), the result was much too busy. For now I rather like this abstract appearance for the tracks.

Comments

A Train Game: Prototypical vs Playable

February 15, 2015

Late last year I was playing Artemis Spaceship Bridge Simulator and, being the sort that likes trains, I thought, wouldn’t it be cool if there were a collaborative game that simulated running a railroad? So late in December I started writing some Swift code, building the game as a Cocoa app. Here’s what it looks like today:

Here’s some video of it from around the first of this year:

The vision is for several players to operate a railroad together. One or two dispatchers, a few engineers cycling between trains. Switching cars in yards, moving trains up and down the mainline, fulfilling orders from industries. A game could run an hour or two, or perhaps a number of sessions over the course of a few weeks.

It’s come a long way over the past two months. There’s a server that clients can connect to and operate the trains, couple and decouple cars. A basic map editor. It’s a lot, or it feels like a lot to me, but at the same time it still feels very primitive. It’s a prototype, or possibly a toy, but not yet a game.

Motivation can be a curious beast, especially with competing priorities. With the basics in place, feature work becomes less satisfying. It’s time to start playing with making it a game.


At its core this game is a simulation of the operating sessions that some model railroaders conduct in their basement, but without the limitation of space. This makes it possible for maps to be very realistic, modeled on real places.

That realism presents some interesting challenges in terms of presenting the information. This is a fairly simple classification yard and already it’s pretty challenging to take in:

Picture those tracks full of cars and weep. I weep for joy and terror: there’s something very pleasing about vast arrays of parallel tracks, as well as extremely daunting if you are expecting me to make sense of it! At the moment my favored approach to this is to add a toggle-able HUD that color codes cars by their destination.

Here’s another area that needs work. Perfectly clear, right?


From here there will of course be more feature work – such as on those switch track signals – but more importantly I plan to work on the model for the industries and yards so that there is something to accomplish in the game, aside from randomly coupling and decoupling cars.

Comments
Tags: ,

Iterating Over a Range of Dates in Swift

September 7, 2014

Updated 2016/8/25: Made a few style tweaks and fixes for Swift 2.2; fixed start date issue (thanks, gist commenters!).

One thing I’ve been wanting to do with Swift is iterate over a range of NSDate objects in a for loop. Something like this:

let startDate = ...
let endDate = ...
for date in startDate...endDate {
    ...
}

While I think it might be possible to do this by making NSDate conform to ForwardIndexType, it would be fairly inflexible. As I understand date arithmetic, to do it right you need a reference to the NSCalendar being used, and of course you need to know how much to ‘step’ the date each time. You could just make it step by days but what if you later want to step by hours?

So I decided on a different approach: create a struct, DateRange, that conforms to SequenceType. It’s not nearly as succinct, but it is much more flexible. Create an instance of the struct using an extension on NSCalendar, as this seems to be in keeping with calendar-dependent date APIs. It looks like this:

let calendar = NSCalendar.currentCalendar()
let startDate = ...
let endDate = ...
let dateRange = calendar.dateRange(startDate: startDate,
                                     endDate: endDate,
                                   stepUnits: .Day,
                                   stepValue: 1)

for date in dateRange {
    print("It's \(date)!")
}

The complete code is below (also in a gist), but first a bit of a disclaimer: this code works, but I half expect to look back on it in a year, cringe, and contemplate deleting this post. My crystal ball of Swift faux pas is cloudy.

Note also that at the time this was written, NSDate did not have any Swift comparison operators built in, so I implemented >. Presumably that will change.

import Foundation

func > (left: NSDate, right: NSDate) -> Bool {
    return left.compare(right) == .OrderedDescending
}

extension NSCalendar {
    func dateRange(startDate startDate: NSDate, endDate: NSDate, stepUnits: NSCalendarUnit, stepValue: Int) -> DateRange {
        let dateRange = DateRange(calendar: self, startDate: startDate, endDate: endDate,
                                  stepUnits: stepUnits, stepValue: stepValue, multiplier: 0)
        return dateRange
    }
}

struct DateRange :SequenceType {
    
    var calendar: NSCalendar
    var startDate: NSDate
    var endDate: NSDate
    var stepUnits: NSCalendarUnit
    var stepValue: Int
    private var multiplier: Int
    
    func generate() -> Generator {
        return Generator(range: self)
    }
    
    struct Generator: GeneratorType {
        
        var range: DateRange
        
        mutating func next() -> NSDate? {
            guard let nextDate = range.calendar.dateByAddingUnit(range.stepUnits,
                                                          value: range.stepValue * range.multiplier,
                                                         toDate: range.startDate,
                                                        options: []) else {
                return nil
            }
            if nextDate > range.endDate {
                return nil
            }
            else {
                range.multiplier += 1
                return nextDate
            }
        }
    }
}

// Usage:
func testDateRange() {
    let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
    let startDate = NSDate(timeIntervalSinceNow: 0)
    let endDate = NSDate(timeIntervalSinceNow: 24*60*60*7-1)
    let dateRange = calendar.dateRange(startDate: startDate,
                                         endDate: endDate,
                                       stepUnits: .Day,
                                       stepValue: 1)
    let datesInRange = Array(dateRange)
    XCTAssertEqual(datesInRange.count, 7, "Expected 7 days")
    XCTAssertEqual(datesInRange.first, startDate, "First date should have been the start date.")
}
Comments
Tags:

Southern 4501 at TVRM

September 6, 2014

Norfolk Southern’s Tumblr and Flickr tend to have some pretty great train photography on them. Recently they’ve had some shots of Southern 4501, which has been restored by the Tennessee Valley Railroad Museum up in Chattanooga.

In particular, this shot of 4501 and 630 at TVRM is pretty sharp: Southern 2-8-0 630 and 2-8-2 4501 Simmer Side By Side. I’d embed them but they aren’t on Flickr.

Update 9/6/2014: They shot some video, too: Southern 4501 and 630 Working Together.

Update 9/7/2014: This overhead shot is pretty outstanding.

We went up to TVRM a couple months ago and had a great visit. The Missionary Ridge Local that they run uses 630 as its power. It’s a 45-minute out-and-back excursion with a 15 minute break in the middle where you watch them turn the locomotive on a turntable and run it back to the (new) front of the train to take it back to the station.

Below is a photo I took during that visit of 630 arriving as NS 9686 kindly passes in the background.

NS 9686 & Southern 630 at Grand Junction

Comments
Tags: ,