Going Native Ana Redmond Founder CTO anaredmond Copenhagen

  • Slides: 46
Download presentation
Going Native Ana Redmond Founder & CTO, @anaredmond Copenhagen Denmark

Going Native Ana Redmond Founder & CTO, @anaredmond Copenhagen Denmark

The problem: Drills and Rote learning

The problem: Drills and Rote learning

The solution Teach concepts Using Manipulatives in Touch-based Games 1. 5 million downloads of

The solution Teach concepts Using Manipulatives in Touch-based Games 1. 5 million downloads of early prototypes (2011 -2015)

Touch-screen Manipulatives teach concepts 120 Math lessons for K-2 with 60+ Games Integrated Audio.

Touch-screen Manipulatives teach concepts 120 Math lessons for K-2 with 60+ Games Integrated Audio. Visual Feedback in every game Pilot Research Result: 1 Year Learning Gain (Cohen’s d: 1. 24, p<. 001) in just 3 Months

infinut Math for Android - Published K, 1 st and 2 nd grade in

infinut Math for Android - Published K, 1 st and 2 nd grade in 2016 -2017 - 6 years to get to 60+ games /w feedback - https: //play. google. com/store/apps/dev? id=5969572456 736192268

In Theory (Dec 2017 NSF Grant Application)

In Theory (Dec 2017 NSF Grant Application)

In Theory (Dec 2017 NSF Grant Application)

In Theory (Dec 2017 NSF Grant Application)

infinut Math for i. OS too! - All 3 grades published in Dec 2018

infinut Math for i. OS too! - All 3 grades published in Dec 2018 - 6 months to port 87, 436 lines of code - https: //apps. apple. com/developer/id 1436758341

Demo App Screens Profile, Tracking, Assignments are not in Kotlin-Native Games E. g. Clothes

Demo App Screens Profile, Tracking, Assignments are not in Kotlin-Native Games E. g. Clothes Line, Place Value Blocks, Fractions etc. are all in Kotlin. Native

Terminology Native Code: Written in Swift on i. OS and Kotlin/Java in Android Kotlin

Terminology Native Code: Written in Swift on i. OS and Kotlin/Java in Android Kotlin Native Code: Written in Kotlin and runs natively on i. OS and Android. Could be platform-specific implementation if it invokes native libraries. Common or Shared Code: Shared Kotlin Native Code that runs on both platforms natively. Written once and compiled to both.

Why Kotlin-Native for infinut Math - Large number of 2 D native games Share

Why Kotlin-Native for infinut Math - Large number of 2 D native games Share game code and feedback logic Code has few external dependencies Code has few screens with native widgets

Architecture

Architecture

Android to i. OS 2 D API Mapping

Android to i. OS 2 D API Mapping

Two ways of sharing code using Kotlin. Native 1. Expects and Actual to invoke

Two ways of sharing code using Kotlin. Native 1. Expects and Actual to invoke low-level platform libraries 2. Interface defined in Kotlin with platformspecific native implementation

Expects & Actual Implemention

Expects & Actual Implemention

e. g. Shapes Drawn using Bezier Paths

e. g. Shapes Drawn using Bezier Paths

Common Game SDK (expects) expect class Path. Display { constructor() fun start. At(x: Double,

Common Game SDK (expects) expect class Path. Display { constructor() fun start. At(x: Double, y: Double) fun quad. To(cx: Double, cy: Double, x: Double, y: Double) fun cubic. To(c 1 x: Double, c 1 y: Double, c 2 x: Double, c 2 y: Double, x: Double, y: Double) fun set. Display(fill. Color: Int, line. Width: Double=0. 0, line. Color: Int=0) fun draw(canvas: Any? ) }

Invoke from Multi-platform Game Code to Draw a Heart Shape fun heart. Path. Display(width:

Invoke from Multi-platform Game Code to Draw a Heart Shape fun heart. Path. Display(width: Double, height: Double) : Path. Display { val p = Path. Display() p. start. At(0. 0, height / 4) p. cubic. To(0. 0, -height/12, width/2, height/4) // (1) p. cubic. To(width/2, -height/12, width, height/4) // (2) p. quad. To(width, height * 2. 5 / 4, width / 2, height) // (3) p. quad. To(0. 0, height * 2. 5 / 4, 0. 0, height / 4) // (4) return p }

Android Implementation of Path. Display import android. graphics. * actual class Path. Display {

Android Implementation of Path. Display import android. graphics. * actual class Path. Display { val path = Path() val paint = Paint(Paint. ANTI_ALIAS_FLAG or Paint. DITHER_FLAG) var line. Width: Double = 0. 0 var line. Color: Int = 0 var fill. Color: Int = 0 i. OS Implementation of Path. Display import kotlinx. cinterop. * import platform. UIKit. * actual class Path. Display { internal var path: UIBezier. Path = UIBezier. Path() internal var line. Width: Double = 0. 0 internal var line. Color: UIColor? = null internal var fill. Color: UIColor? = null

Android Implementation of Path. Display functions i. OS Implementation of Path. Display functions actual

Android Implementation of Path. Display functions i. OS Implementation of Path. Display functions actual fun start. At( x: Double, y: Double) { path. move. To(x. to. Float(), y. to. Float()) } actual fun start. At( x: Double, y: Double) { path. move. To. Point( CGPoint. Make(x, y)) } actual fun quad. To(. . . ) { path. quad. To(. . . ) } actual fun quad. To(. . . ) { path. add. Quad. Curve. To. Point(. . . ) } actual fun cubic. To(. . . ) { path. cubic. To(. . . ) } actual fun cubic. To(. . . ) { path. add. Curve. To. Point(. . . ) }

Android Implementation of Path. Display. draw i. OS Implementation of Path. Display. draw actual

Android Implementation of Path. Display. draw i. OS Implementation of Path. Display. draw actual fun draw(canvas: Any? ) { val c = canvas!! as Canvas if(line. Color!=0) { paint. set. Color(line. Color) paint. set. Style( Paint. Style. STROKE) paint. set. Stroke. Width( line. Width. to. Float()) c. draw. Path(path, paint) }. . . } actual fun draw(canvas: Any? ) { line. Color? . let { path? . line. Width = line. Width it. set. Stroke() path? . stroke() } . . . }

Invoke from Common Game Code to Draw a Heart Shape fun heart. Path. Display(width:

Invoke from Common Game Code to Draw a Heart Shape fun heart. Path. Display(width: Double, height: Double) : Path. Display { val p = Path. Display() p. start. At(0. 0, height / 4) p. cubic. To(0. 0, -height/12, width/2, height/4) // (1). . . }

Image Load Android Implementation Image Load i. OS Implementation actual fun load(file: String, width:

Image Load Android Implementation Image Load i. OS Implementation actual fun load(file: String, width: Double, height: Double, asset. Manager: Any? ) { val am = asset. Manager!! as Asset. Manager val filepath = "image/$filename. png" actual fun load(file: String, width: Double, height: Double, asset. Manager: Any? ) { image = UIImage. image. Named("image/$file. png") val size. It = asset. Manager. open(filepath) options. in. Just. Decode. Bounds = true Bitmap. Factory. decode. Stream(size. It, null, options) size. It. close() //calculate sample size, then load image using that val load. It = asset. Manager. open(filepath) options. in. Just. Decode. Bounds = false val bitmap = Bitmap. Factory. decode. Stream(load. It, null, options) load. It. close() //resize image to display size image. Bitmap = Bitmap. create. Scaled. Bitmap(bitmap, sx, sy, true) } //Get size from loaded image var image. Width = 0. 0 var image. Height = 0. 0 image? . size? . use. Contents { image. Width = this. width image. Height = this. height } //resize image to display size (size calculation not shown) val rect = CGRect. Make(0. 0, this. width, this. height) val size = CGSize. Make(this. width, this. height) UIGraphics. Begin. Image. Context(size); image? . draw. In. Rect(rect); image = UIGraphics. Get. Image. From. Current. Image. Context(); UIGraphics. End. Image. Context(); }

Image Draw Android Implementation actual fun draw(matrix: Transform, canvas: Any? ) { val c

Image Draw Android Implementation actual fun draw(matrix: Transform, canvas: Any? ) { val c = canvas!! as Canvas Image Draw i. OS Implementation actual fun draw(matrix: Transform, canvas: Any? ) { var context = UIGraphics. Get. Current. Context() CGContext. Save. GState(context) CGContext. Concat. CTM(context, matrix) image? . draw. At. Point(loc) CGContext. Restore. GState(context) if(matrix. filter. Bitmap || alphavalue<255) { c. draw. Bitmap(image. Bitmap, matrix, paint) } else { c. draw. Bitmap(image. Bitmap, matrix, null) } } }

Transform Android Implementation actual class Transform { Transform i. OS Implementation actual class Transform

Transform Android Implementation actual class Transform { Transform i. OS Implementation actual class Transform { internal val matrix: Matrix = Matrix() internal var filter. Bitmap = false; internal var matrix = CGAffine. Transform. Identity. read. Value() actual fun post. Translate(left: Double, top: Double) { matrix. post. Translate(left. to. Float(), top. to. Float()) } actual fun post. Translate(left: Double, top: Double) { var transform = CGAffine. Transform. Make. Translation( left, top) matrix = CGAffine. Transform. Concat( matrix, transform) }

Expects and Actual Loading and drawing images Transforming i. e. rotating, translating, scaling images

Expects and Actual Loading and drawing images Transforming i. e. rotating, translating, scaling images Drawing text in different fonts Playing audio instructions

Interface implemented in Native

Interface implemented in Native

Back button in the game

Back button in the game

Game Rendering on Android

Game Rendering on Android

Game Rendering on i. OS

Game Rendering on i. OS

Shared Interface in Kotlin interface Game. Player { fun get. Media. Manager(): Media. Manager

Shared Interface in Kotlin interface Game. Player { fun get. Media. Manager(): Media. Manager fun get. Sound. Manager(): Sound. Manager fun get. Asset. Manager() : Any? fun back() fun get. Touch. Events(): List<Touch. Event> fun save. Exercise. State(inc. Mistakes: Float, inc. Time: Int, completed: Boolean) }

Invoke from Multi-platform Game Code to handle User Input abstract class Game(player: Game. Player)

Invoke from Multi-platform Game Code to handle User Input abstract class Game(player: Game. Player) { var player: Weak. Ref<Game. Player> init { this. player = Weak. Ref<Game. Player> (player) } //Used later: if(player. get()==null) return; val player = this. player. get()!! if (back. Button. touch_up(event. x, event. y, event. pointer)) { player. get. Media. Manager(). stop() player. back() return }

Back Press Android Implementation class Render. View(context: Context, attrs: Attribute. Set) : Surface. View(context,

Back Press Android Implementation class Render. View(context: Context, attrs: Attribute. Set) : Surface. View(context, attrs), Game. Player { Back Press i. OS Implementation public class Screen. View. Controller: UIView. Controller, Game. Player { override fun back() { context. run. On. Ui. Thread { context. on. Back. Pressed() } } } public func back() { dismiss ( animated: true, completion: nil) } }

Game Create Android Implementation class Render. View(context: Context, attrs: Attribute. Set) : Surface. View(context,

Game Create Android Implementation class Render. View(context: Context, attrs: Attribute. Set) : Surface. View(context, attrs), Game. Player { Game Create i. OS Implementation public class Screen. View: UIView { var game: Game? = nil weak var player: Game. Player? override public func layout. Subviews() { … self. game = Stamp. Game(player: player, gifts: gifts, instruction: text) … } protected var game = Atomic. Reference<Game>() init { game. set( Stamp. Game(this, gifts, text) } } }

Preventing Memory Leak on i. OS this. player = Weak. Ref <Game. Player> (player)

Preventing Memory Leak on i. OS this. player = Weak. Ref <Game. Player> (player)

Common Interface implemented in Native Touch events from native view to the shared game

Common Interface implemented in Native Touch events from native view to the shared game screen Invoking Firebase from shared game screen to save data Accessing application scoped objects from game screen

Two ways of sharing code using Kotlin Native

Two ways of sharing code using Kotlin Native

Source: https: //appadvice. com/appnn/2014/05/meet-cider-a-research-project-that-allows-android-devices-to-run-ios-apps

Source: https: //appadvice. com/appnn/2014/05/meet-cider-a-research-project-that-allows-android-devices-to-run-ios-apps

Lesson 1. No Objects public class Sound. Manager { static private Sound. Manager instance;

Lesson 1. No Objects public class Sound. Manager { static private Sound. Manager instance; //Singleton private Sound. Pool m. Sound. Pool; public static void load. Sounds(final Context the. Context, final Set<Integer> sounds. To. Load) { … m. Sound. Pool. load(the. Context, soundid, 1); } private void play. Sound(int index) { m. Sound. Pool. play(index, 1, 1, 1, 0, 1 f); } public void play. Monkey(){play. Sound(monkey); } public void play. Chime(){play. Sound(chime); } }

Sound. Manager creation on Android abstract class Math. Application : android. app. Application() {

Sound. Manager creation on Android abstract class Math. Application : android. app. Application() { private lateinit var sound. Manager: Atomic. Reference<Sound. Manager> override fun on. Create() { super. on. Create() sound. Manager = Atomic. Reference( Sound. Manager(assets)) } Sound. Manager creation on i. OS class App. Delegate: UIResponder, UIApplication. Delegate { var sound. Manager: Sound. Manager = Sound. Manager(asset. Manager: nil) }

Shared Interface in Kotlin interface Game. Player { fun get. Media. Manager(): Media. Manager

Shared Interface in Kotlin interface Game. Player { fun get. Media. Manager(): Media. Manager fun get. Sound. Manager(): Sound. Manager fun get. Asset. Manager() : Any? fun back() fun get. Touch. Events(): List<Touch. Event> fun save. Exercise. State(inc. Mistakes: Float, inc. Time: Int, completed: Boolean) }

Get and use the Sound. Manager in the Game open class Clothes. Line. Number.

Get and use the Sound. Manager in the Game open class Clothes. Line. Number. Game(player: Game. Player, internal var exercise: Clothes. Line. Exercise) : Game(player) { fun get. Sound. Manager(): Sound. Manager? { return player. get()? . get. Sound. Manager() } override fun touch. Up(x: Double, y: Double, pointer: Int) { … if (exercise. theme ==Numbe. Line. Theme. DRESS) get. Sound. Manager()? . play. Chime() else if (exercise. theme == Numbe. Line. Theme. MONKEY) get. Sound. Manager()? . play. Monkey(). . . } }

Lesson 2. Array vs. List Initialization of Kotlin Array in Swift: Initialization of Kotlin

Lesson 2. Array vs. List Initialization of Kotlin Array in Swift: Initialization of Kotlin List in Swift: let gifts = Stdlib. Array( size: 1, init: {_ in Gift(. . . ) } ) let gifts = [ Gift(. . . ) ]

Lesson 3. Initialization Order open class Parent { init. Child() } open fun init.

Lesson 3. Initialization Order open class Parent { init. Child() } open fun init. Child() {. . . } } class Child: Parent() { var c: String = "Declare" init { } override fun init. Child() { c = "Initialize" } }

Links Using Surface. View to build 2 D games, Droidcon NY, 2015 https: //www.

Links Using Surface. View to build 2 D games, Droidcon NY, 2015 https: //www. youtube. com/watch? v=KBkl. J 76 d. D 8 I 12 -part series on porting 2 D educational Games to i. OS using Kotlin Native: https: //medium. com/@anaredmond/cross-platform-game-1 -of-afbb 8 b 16 c 3 f 7 A post with this presentation will be added soon to: https: //medium. com/@anaredmond

THANK YOU AND REMEMBER TO VOTE Ana Redmond @anaredmond #Kotlin. Conf

THANK YOU AND REMEMBER TO VOTE Ana Redmond @anaredmond #Kotlin. Conf