├── .gitignore ├── .gitmodules ├── README.md ├── config.xcconfig ├── ios_handmade.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── ios_handmade.xcscmblueprint ├── ios_handmade ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── launch_image.launchimage │ │ └── Contents.json ├── Info.plist ├── build.sh ├── ios_app_delegate.h ├── ios_app_delegate.m └── ios_main.m └── screenshots ├── day40.png ├── day40_2.png ├── day40_3.png ├── day40_4.png ├── day52.png └── day69.png /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | build/ 4 | handmade/data 5 | 6 | # Xcode 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | xcuserdata 16 | *.xccheckout 17 | *.moved-aside 18 | DerivedData 19 | *.xcuserstate 20 | *.hmap 21 | *.ipa 22 | 23 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "handmade"] 2 | path = handmade 3 | url = https://github.com/HandmadeHero/cpp.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ios_handmade 2 | [Paused] iOS port of Casey Muratori's awesome [Handmade Hero](https://handmadehero.org/) project. 3 | 4 | For those coming here to find a "proper" way to setup an iOS platform layer, this sadly won't suffice. Most major areas (rendering, audio, touch input) have issues in this codebase. For a (more) updated account of those systems, refer to my repo "ios_test" which attempts to nail-down some of the oddities of iOS. 5 | 6 | ### Current status - Day 69 7 | 8 | ![screenshot](/screenshots/day69.png) 9 | 10 | - [x] CoreAudio sound 11 | - [x] Quartz graphics. Currently blitting game buffer directly to screen without scaling. 12 | - [x] UIKit touch input 13 | - [ ] OpenGLES graphics 14 | - [X] Live-loop replays 15 | - [ ] Live-loop editing. So iOS 9 *does* support dynamic libraries; however, for the platform layer to have visability of a newly-compiled game library, we have to be running on Simulator (and even then, not sure if it's possible). 16 | 17 | Code is still just debug-quality. 18 | 19 | ### Prerequisites 20 | * Preorder of Handmade Hero! This is possibly the coolest game project ever, and is the only way to get access to the source code which you'll need to build this game. Handmade Hero can be preordered from the project's main site here ([https://handmadehero.org/](https://handmadehero.org/)) 21 | * A Mac with Xcode and the latest SDK (current 9.2) 22 | 23 | ### How-to (Changes from last update) 24 | 1. Clone this repository with `clone --recursive`. This will also clone Casey's GitHub repo into the **handmade/** directory (*if you have access!*) 25 | 2. Unzip the game's assets into **handmade/data/**. 26 | 3. Build using Xcode and run on the Simulator (or on a device if you have an iOS Developers Membership). I don't know how to run iOS app any other way other than through Xcode. Depending on the day you're building, you may have to manually add/remove compile targets and files from the project. Currently, the project is setup for day 69. 27 | 28 | ### Directory structure ### 29 | 30 | ``` 31 | ios_handmade/ios_handmade - my code 32 | ios_handmade/handmade/code - Casey's code 33 | ios_handmade/handmade/data - Casey's unzipped data 34 | ``` 35 | 36 | ### Controls ### 37 | * Analog left-thumbstick maps to movement 38 | * 4 blue buttons on lower-right map to Action[Up/Down/Left/Right] 39 | * 2 blue buttons on top-left map to Back/Start 40 | * Red button in top-right is for live-loop replay recording and playback 41 | 42 | ### Licensing 43 | Casey Muratori is the author of Handmade Hero. I am writing this port with his permission. Please checkout the project here ([https://handmadehero.org/](https://handmadehero.org/)). 44 | 45 | ### Contributing 46 | This is my first OS project - I'm doing this for fun, to learn about the iOS platform. If you have any thoughts, concerns, tips or tricks, feel free to contact me at [zach@okeefe.io](mailto:zach@okeefe.io). 47 | -------------------------------------------------------------------------------- /config.xcconfig: -------------------------------------------------------------------------------- 1 | //:configuration = release 2 | 3 | //:configuration = debug 4 | OTHER_CFLAGS = -DIOS_HANDMADE_DEBUG_INPUT=1 -DHANDMADE_SLOW=1 -DHANDMADE_INTERNAL=1 -Wall -Wno-null-dereference -Wno-tautological-compare -Wno-unused-variable -Wno-c++11-compat-deprecated-writable-strings 5 | 6 | //:completeSettings = none 7 | -------------------------------------------------------------------------------- /ios_handmade.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E0B1F8331A858DE900BFD1AC /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0B1F8321A858DE900BFD1AC /* UIKit.framework */; }; 11 | E0B1F8351A858DF400BFD1AC /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0B1F8341A858DF400BFD1AC /* CoreGraphics.framework */; }; 12 | E0B1F8371A858DFE00BFD1AC /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0B1F8361A858DFE00BFD1AC /* AudioToolbox.framework */; }; 13 | E0F04B3F1C5326BF00CF3C4A /* handmade.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E0F04AF11C5326BF00CF3C4A /* handmade.cpp */; }; 14 | E0F04B401C5326BF00CF3C4A /* handmade.h in Headers */ = {isa = PBXBuildFile; fileRef = E0F04AF21C5326BF00CF3C4A /* handmade.h */; }; 15 | E0F04B421C5326BF00CF3C4A /* handmade_entity.h in Headers */ = {isa = PBXBuildFile; fileRef = E0F04AF41C5326BF00CF3C4A /* handmade_entity.h */; }; 16 | E0F04B431C5326BF00CF3C4A /* handmade_intrinsics.h in Headers */ = {isa = PBXBuildFile; fileRef = E0F04AF51C5326BF00CF3C4A /* handmade_intrinsics.h */; }; 17 | E0F04B441C5326BF00CF3C4A /* handmade_math.h in Headers */ = {isa = PBXBuildFile; fileRef = E0F04AF61C5326BF00CF3C4A /* handmade_math.h */; }; 18 | E0F04B451C5326BF00CF3C4A /* handmade_platform.h in Headers */ = {isa = PBXBuildFile; fileRef = E0F04AF71C5326BF00CF3C4A /* handmade_platform.h */; }; 19 | E0F04B461C5326BF00CF3C4A /* handmade_random.h in Headers */ = {isa = PBXBuildFile; fileRef = E0F04AF81C5326BF00CF3C4A /* handmade_random.h */; }; 20 | E0F04B481C5326BF00CF3C4A /* handmade_sim_region.h in Headers */ = {isa = PBXBuildFile; fileRef = E0F04AFA1C5326BF00CF3C4A /* handmade_sim_region.h */; }; 21 | E0F04B4A1C5326BF00CF3C4A /* handmade_world.h in Headers */ = {isa = PBXBuildFile; fileRef = E0F04AFC1C5326BF00CF3C4A /* handmade_world.h */; }; 22 | E0F04B4F1C5326BF00CF3C4A /* win32_handmade.h in Headers */ = {isa = PBXBuildFile; fileRef = E0F04B011C5326BF00CF3C4A /* win32_handmade.h */; }; 23 | E0F04B861C53270900CF3C4A /* test_background.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B041C5326BF00CF3C4A /* test_background.bmp */; }; 24 | E0F04B871C53270900CF3C4A /* test_hero_back_cape.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B051C5326BF00CF3C4A /* test_hero_back_cape.bmp */; }; 25 | E0F04B881C53270900CF3C4A /* test_hero_back_head.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B061C5326BF00CF3C4A /* test_hero_back_head.bmp */; }; 26 | E0F04B891C53270900CF3C4A /* test_hero_back_torso.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B071C5326BF00CF3C4A /* test_hero_back_torso.bmp */; }; 27 | E0F04B8A1C53270900CF3C4A /* test_hero_front_cape.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B081C5326BF00CF3C4A /* test_hero_front_cape.bmp */; }; 28 | E0F04B8B1C53270900CF3C4A /* test_hero_front_head.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B091C5326BF00CF3C4A /* test_hero_front_head.bmp */; }; 29 | E0F04B8C1C53270900CF3C4A /* test_hero_front_torso.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B0A1C5326BF00CF3C4A /* test_hero_front_torso.bmp */; }; 30 | E0F04B8D1C53270900CF3C4A /* test_hero_left_cape.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B0B1C5326BF00CF3C4A /* test_hero_left_cape.bmp */; }; 31 | E0F04B8E1C53270900CF3C4A /* test_hero_left_head.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B0C1C5326BF00CF3C4A /* test_hero_left_head.bmp */; }; 32 | E0F04B8F1C53270900CF3C4A /* test_hero_left_torso.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B0D1C5326BF00CF3C4A /* test_hero_left_torso.bmp */; }; 33 | E0F04B901C53270900CF3C4A /* test_hero_right_cape.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B0E1C5326BF00CF3C4A /* test_hero_right_cape.bmp */; }; 34 | E0F04B911C53270900CF3C4A /* test_hero_right_head.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B0F1C5326BF00CF3C4A /* test_hero_right_head.bmp */; }; 35 | E0F04B921C53270900CF3C4A /* test_hero_right_torso.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B101C5326BF00CF3C4A /* test_hero_right_torso.bmp */; }; 36 | E0F04B931C53270900CF3C4A /* test_hero_shadow.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B111C5326BF00CF3C4A /* test_hero_shadow.bmp */; }; 37 | E0F04B941C53270900CF3C4A /* test_scene_layer_00.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B121C5326BF00CF3C4A /* test_scene_layer_00.bmp */; }; 38 | E0F04B951C53270900CF3C4A /* test_scene_layer_01.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B131C5326BF00CF3C4A /* test_scene_layer_01.bmp */; }; 39 | E0F04B961C53270900CF3C4A /* test_scene_layer_02.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B141C5326BF00CF3C4A /* test_scene_layer_02.bmp */; }; 40 | E0F04B971C53270900CF3C4A /* test_scene_layer_03.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B151C5326BF00CF3C4A /* test_scene_layer_03.bmp */; }; 41 | E0F04B981C53270900CF3C4A /* test_scene_layer_04.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B161C5326BF00CF3C4A /* test_scene_layer_04.bmp */; }; 42 | E0F04B991C53270900CF3C4A /* grass00.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B181C5326BF00CF3C4A /* grass00.bmp */; }; 43 | E0F04B9A1C53270900CF3C4A /* grass01.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B191C5326BF00CF3C4A /* grass01.bmp */; }; 44 | E0F04B9B1C53270900CF3C4A /* ground00.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B1A1C5326BF00CF3C4A /* ground00.bmp */; }; 45 | E0F04B9C1C53270900CF3C4A /* ground01.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B1B1C5326BF00CF3C4A /* ground01.bmp */; }; 46 | E0F04B9D1C53270900CF3C4A /* ground02.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B1C1C5326BF00CF3C4A /* ground02.bmp */; }; 47 | E0F04B9E1C53270900CF3C4A /* ground03.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B1D1C5326BF00CF3C4A /* ground03.bmp */; }; 48 | E0F04B9F1C53270900CF3C4A /* rock00.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B1E1C5326BF00CF3C4A /* rock00.bmp */; }; 49 | E0F04BA01C53270900CF3C4A /* rock01.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B1F1C5326BF00CF3C4A /* rock01.bmp */; }; 50 | E0F04BA11C53270900CF3C4A /* rock02.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B201C5326BF00CF3C4A /* rock02.bmp */; }; 51 | E0F04BA21C53270900CF3C4A /* rock03.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B211C5326BF00CF3C4A /* rock03.bmp */; }; 52 | E0F04BA31C53270900CF3C4A /* tree00.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B221C5326BF00CF3C4A /* tree00.bmp */; }; 53 | E0F04BA41C53270900CF3C4A /* tree01.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B231C5326BF00CF3C4A /* tree01.bmp */; }; 54 | E0F04BA51C53270900CF3C4A /* tree02.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B241C5326BF00CF3C4A /* tree02.bmp */; }; 55 | E0F04BA61C53270900CF3C4A /* tuft00.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B251C5326BF00CF3C4A /* tuft00.bmp */; }; 56 | E0F04BA71C53270900CF3C4A /* tuft01.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B261C5326BF00CF3C4A /* tuft01.bmp */; }; 57 | E0F04BA81C53270900CF3C4A /* tuft02.bmp in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B271C5326BF00CF3C4A /* tuft02.bmp */; }; 58 | E0F04BA91C53270900CF3C4A /* bloop_00.wav in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B291C5326BF00CF3C4A /* bloop_00.wav */; }; 59 | E0F04BAA1C53270900CF3C4A /* bloop_01.wav in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B2A1C5326BF00CF3C4A /* bloop_01.wav */; }; 60 | E0F04BAB1C53270900CF3C4A /* bloop_02.wav in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B2B1C5326BF00CF3C4A /* bloop_02.wav */; }; 61 | E0F04BAC1C53270900CF3C4A /* bloop_03.wav in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B2C1C5326BF00CF3C4A /* bloop_03.wav */; }; 62 | E0F04BAD1C53270900CF3C4A /* bloop_04.wav in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B2D1C5326BF00CF3C4A /* bloop_04.wav */; }; 63 | E0F04BAE1C53270900CF3C4A /* crack_00.wav in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B2E1C5326BF00CF3C4A /* crack_00.wav */; }; 64 | E0F04BAF1C53270900CF3C4A /* drop_00.wav in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B2F1C5326BF00CF3C4A /* drop_00.wav */; }; 65 | E0F04BB01C53270900CF3C4A /* glide_00.wav in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B301C5326BF00CF3C4A /* glide_00.wav */; }; 66 | E0F04BB11C53270900CF3C4A /* music_test.wav in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B311C5326BF00CF3C4A /* music_test.wav */; }; 67 | E0F04BB21C53270900CF3C4A /* puhp_00.wav in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B321C5326BF00CF3C4A /* puhp_00.wav */; }; 68 | E0F04BB31C53270900CF3C4A /* puhp_01.wav in Resources */ = {isa = PBXBuildFile; fileRef = E0F04B331C5326BF00CF3C4A /* puhp_01.wav */; }; 69 | E0F3C4481A82E85200A6D5EF /* ios_main.m in Sources */ = {isa = PBXBuildFile; fileRef = E0F3C4471A82E85200A6D5EF /* ios_main.m */; }; 70 | E0F3C44B1A82E85200A6D5EF /* ios_app_delegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E0F3C44A1A82E85200A6D5EF /* ios_app_delegate.m */; }; 71 | /* End PBXBuildFile section */ 72 | 73 | /* Begin PBXFileReference section */ 74 | E0AC989A1A96C6BB009C64F9 /* config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = config.xcconfig; sourceTree = ""; }; 75 | E0B1F8321A858DE900BFD1AC /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 76 | E0B1F8341A858DF400BFD1AC /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 77 | E0B1F8361A858DFE00BFD1AC /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 78 | E0F04AEE1C5326BF00CF3C4A /* .gitignore */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; 79 | E0F04AF01C5326BF00CF3C4A /* build.bat */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = build.bat; sourceTree = ""; }; 80 | E0F04AF11C5326BF00CF3C4A /* handmade.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = handmade.cpp; sourceTree = ""; }; 81 | E0F04AF21C5326BF00CF3C4A /* handmade.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = handmade.h; sourceTree = ""; }; 82 | E0F04AF31C5326BF00CF3C4A /* handmade_entity.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = handmade_entity.cpp; sourceTree = ""; }; 83 | E0F04AF41C5326BF00CF3C4A /* handmade_entity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = handmade_entity.h; sourceTree = ""; }; 84 | E0F04AF51C5326BF00CF3C4A /* handmade_intrinsics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = handmade_intrinsics.h; sourceTree = ""; }; 85 | E0F04AF61C5326BF00CF3C4A /* handmade_math.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = handmade_math.h; sourceTree = ""; }; 86 | E0F04AF71C5326BF00CF3C4A /* handmade_platform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = handmade_platform.h; sourceTree = ""; }; 87 | E0F04AF81C5326BF00CF3C4A /* handmade_random.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = handmade_random.h; sourceTree = ""; }; 88 | E0F04AF91C5326BF00CF3C4A /* handmade_sim_region.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = handmade_sim_region.cpp; sourceTree = ""; }; 89 | E0F04AFA1C5326BF00CF3C4A /* handmade_sim_region.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = handmade_sim_region.h; sourceTree = ""; }; 90 | E0F04AFB1C5326BF00CF3C4A /* handmade_world.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = handmade_world.cpp; sourceTree = ""; }; 91 | E0F04AFC1C5326BF00CF3C4A /* handmade_world.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = handmade_world.h; sourceTree = ""; }; 92 | E0F04AFD1C5326BF00CF3C4A /* linux_handmade.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = linux_handmade.cpp; sourceTree = ""; }; 93 | E0F04AFE1C5326BF00CF3C4A /* static_check.bat */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = static_check.bat; sourceTree = ""; }; 94 | E0F04AFF1C5326BF00CF3C4A /* todo_check.bat */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = todo_check.bat; sourceTree = ""; }; 95 | E0F04B001C5326BF00CF3C4A /* win32_handmade.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = win32_handmade.cpp; sourceTree = ""; }; 96 | E0F04B011C5326BF00CF3C4A /* win32_handmade.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = win32_handmade.h; sourceTree = ""; }; 97 | E0F04B041C5326BF00CF3C4A /* test_background.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_background.bmp; sourceTree = ""; }; 98 | E0F04B051C5326BF00CF3C4A /* test_hero_back_cape.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_hero_back_cape.bmp; sourceTree = ""; }; 99 | E0F04B061C5326BF00CF3C4A /* test_hero_back_head.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_hero_back_head.bmp; sourceTree = ""; }; 100 | E0F04B071C5326BF00CF3C4A /* test_hero_back_torso.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_hero_back_torso.bmp; sourceTree = ""; }; 101 | E0F04B081C5326BF00CF3C4A /* test_hero_front_cape.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_hero_front_cape.bmp; sourceTree = ""; }; 102 | E0F04B091C5326BF00CF3C4A /* test_hero_front_head.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_hero_front_head.bmp; sourceTree = ""; }; 103 | E0F04B0A1C5326BF00CF3C4A /* test_hero_front_torso.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_hero_front_torso.bmp; sourceTree = ""; }; 104 | E0F04B0B1C5326BF00CF3C4A /* test_hero_left_cape.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_hero_left_cape.bmp; sourceTree = ""; }; 105 | E0F04B0C1C5326BF00CF3C4A /* test_hero_left_head.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_hero_left_head.bmp; sourceTree = ""; }; 106 | E0F04B0D1C5326BF00CF3C4A /* test_hero_left_torso.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_hero_left_torso.bmp; sourceTree = ""; }; 107 | E0F04B0E1C5326BF00CF3C4A /* test_hero_right_cape.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_hero_right_cape.bmp; sourceTree = ""; }; 108 | E0F04B0F1C5326BF00CF3C4A /* test_hero_right_head.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_hero_right_head.bmp; sourceTree = ""; }; 109 | E0F04B101C5326BF00CF3C4A /* test_hero_right_torso.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_hero_right_torso.bmp; sourceTree = ""; }; 110 | E0F04B111C5326BF00CF3C4A /* test_hero_shadow.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_hero_shadow.bmp; sourceTree = ""; }; 111 | E0F04B121C5326BF00CF3C4A /* test_scene_layer_00.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_scene_layer_00.bmp; sourceTree = ""; }; 112 | E0F04B131C5326BF00CF3C4A /* test_scene_layer_01.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_scene_layer_01.bmp; sourceTree = ""; }; 113 | E0F04B141C5326BF00CF3C4A /* test_scene_layer_02.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_scene_layer_02.bmp; sourceTree = ""; }; 114 | E0F04B151C5326BF00CF3C4A /* test_scene_layer_03.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_scene_layer_03.bmp; sourceTree = ""; }; 115 | E0F04B161C5326BF00CF3C4A /* test_scene_layer_04.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = test_scene_layer_04.bmp; sourceTree = ""; }; 116 | E0F04B181C5326BF00CF3C4A /* grass00.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = grass00.bmp; sourceTree = ""; }; 117 | E0F04B191C5326BF00CF3C4A /* grass01.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = grass01.bmp; sourceTree = ""; }; 118 | E0F04B1A1C5326BF00CF3C4A /* ground00.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = ground00.bmp; sourceTree = ""; }; 119 | E0F04B1B1C5326BF00CF3C4A /* ground01.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = ground01.bmp; sourceTree = ""; }; 120 | E0F04B1C1C5326BF00CF3C4A /* ground02.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = ground02.bmp; sourceTree = ""; }; 121 | E0F04B1D1C5326BF00CF3C4A /* ground03.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = ground03.bmp; sourceTree = ""; }; 122 | E0F04B1E1C5326BF00CF3C4A /* rock00.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = rock00.bmp; sourceTree = ""; }; 123 | E0F04B1F1C5326BF00CF3C4A /* rock01.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = rock01.bmp; sourceTree = ""; }; 124 | E0F04B201C5326BF00CF3C4A /* rock02.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = rock02.bmp; sourceTree = ""; }; 125 | E0F04B211C5326BF00CF3C4A /* rock03.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = rock03.bmp; sourceTree = ""; }; 126 | E0F04B221C5326BF00CF3C4A /* tree00.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = tree00.bmp; sourceTree = ""; }; 127 | E0F04B231C5326BF00CF3C4A /* tree01.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = tree01.bmp; sourceTree = ""; }; 128 | E0F04B241C5326BF00CF3C4A /* tree02.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = tree02.bmp; sourceTree = ""; }; 129 | E0F04B251C5326BF00CF3C4A /* tuft00.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = tuft00.bmp; sourceTree = ""; }; 130 | E0F04B261C5326BF00CF3C4A /* tuft01.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = tuft01.bmp; sourceTree = ""; }; 131 | E0F04B271C5326BF00CF3C4A /* tuft02.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = tuft02.bmp; sourceTree = ""; }; 132 | E0F04B291C5326BF00CF3C4A /* bloop_00.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = bloop_00.wav; sourceTree = ""; }; 133 | E0F04B2A1C5326BF00CF3C4A /* bloop_01.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = bloop_01.wav; sourceTree = ""; }; 134 | E0F04B2B1C5326BF00CF3C4A /* bloop_02.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = bloop_02.wav; sourceTree = ""; }; 135 | E0F04B2C1C5326BF00CF3C4A /* bloop_03.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = bloop_03.wav; sourceTree = ""; }; 136 | E0F04B2D1C5326BF00CF3C4A /* bloop_04.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = bloop_04.wav; sourceTree = ""; }; 137 | E0F04B2E1C5326BF00CF3C4A /* crack_00.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = crack_00.wav; sourceTree = ""; }; 138 | E0F04B2F1C5326BF00CF3C4A /* drop_00.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = drop_00.wav; sourceTree = ""; }; 139 | E0F04B301C5326BF00CF3C4A /* glide_00.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = glide_00.wav; sourceTree = ""; }; 140 | E0F04B311C5326BF00CF3C4A /* music_test.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = music_test.wav; sourceTree = ""; }; 141 | E0F04B321C5326BF00CF3C4A /* puhp_00.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = puhp_00.wav; sourceTree = ""; }; 142 | E0F04B331C5326BF00CF3C4A /* puhp_01.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = puhp_01.wav; sourceTree = ""; }; 143 | E0F04B341C5326BF00CF3C4A /* day.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = day.txt; sourceTree = ""; }; 144 | E0F04B351C5326BF00CF3C4A /* license.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = license.txt; sourceTree = ""; }; 145 | E0F04B371C5326BF00CF3C4A /* .emacs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .emacs; sourceTree = ""; }; 146 | E0F04B381C5326BF00CF3C4A /* emacs.bat */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = emacs.bat; sourceTree = ""; }; 147 | E0F04B391C5326BF00CF3C4A /* emacs_keybindings.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = emacs_keybindings.txt; sourceTree = ""; }; 148 | E0F04B3A1C5326BF00CF3C4A /* handmade_hero_settings_msvc2013.vssettings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = handmade_hero_settings_msvc2013.vssettings; sourceTree = ""; }; 149 | E0F04B3B1C5326BF00CF3C4A /* shell.bat */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = shell.bat; sourceTree = ""; }; 150 | E0F04B3C1C5326BF00CF3C4A /* readme.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = readme.txt; sourceTree = ""; }; 151 | E0F3C4421A82E85200A6D5EF /* ios_handmade.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ios_handmade.app; sourceTree = BUILT_PRODUCTS_DIR; }; 152 | E0F3C4461A82E85200A6D5EF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 153 | E0F3C4471A82E85200A6D5EF /* ios_main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ios_main.m; sourceTree = ""; }; 154 | E0F3C4491A82E85200A6D5EF /* ios_app_delegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ios_app_delegate.h; sourceTree = ""; }; 155 | E0F3C44A1A82E85200A6D5EF /* ios_app_delegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ios_app_delegate.m; sourceTree = ""; }; 156 | E0F3C4561A82E85200A6D5EF /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 157 | /* End PBXFileReference section */ 158 | 159 | /* Begin PBXFrameworksBuildPhase section */ 160 | E0F3C43F1A82E85200A6D5EF /* Frameworks */ = { 161 | isa = PBXFrameworksBuildPhase; 162 | buildActionMask = 2147483647; 163 | files = ( 164 | E0B1F8371A858DFE00BFD1AC /* AudioToolbox.framework in Frameworks */, 165 | E0B1F8351A858DF400BFD1AC /* CoreGraphics.framework in Frameworks */, 166 | E0B1F8331A858DE900BFD1AC /* UIKit.framework in Frameworks */, 167 | ); 168 | runOnlyForDeploymentPostprocessing = 0; 169 | }; 170 | /* End PBXFrameworksBuildPhase section */ 171 | 172 | /* Begin PBXGroup section */ 173 | E0F04AED1C5326BF00CF3C4A /* handmade */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | E0F04AEE1C5326BF00CF3C4A /* .gitignore */, 177 | E0F04AEF1C5326BF00CF3C4A /* code */, 178 | E0F04B021C5326BF00CF3C4A /* data */, 179 | E0F04B341C5326BF00CF3C4A /* day.txt */, 180 | E0F04B351C5326BF00CF3C4A /* license.txt */, 181 | E0F04B361C5326BF00CF3C4A /* misc */, 182 | E0F04B3C1C5326BF00CF3C4A /* readme.txt */, 183 | ); 184 | path = handmade; 185 | sourceTree = ""; 186 | }; 187 | E0F04AEF1C5326BF00CF3C4A /* code */ = { 188 | isa = PBXGroup; 189 | children = ( 190 | E0F04AF01C5326BF00CF3C4A /* build.bat */, 191 | E0F04AF11C5326BF00CF3C4A /* handmade.cpp */, 192 | E0F04AF21C5326BF00CF3C4A /* handmade.h */, 193 | E0F04AF31C5326BF00CF3C4A /* handmade_entity.cpp */, 194 | E0F04AF41C5326BF00CF3C4A /* handmade_entity.h */, 195 | E0F04AF51C5326BF00CF3C4A /* handmade_intrinsics.h */, 196 | E0F04AF61C5326BF00CF3C4A /* handmade_math.h */, 197 | E0F04AF71C5326BF00CF3C4A /* handmade_platform.h */, 198 | E0F04AF81C5326BF00CF3C4A /* handmade_random.h */, 199 | E0F04AF91C5326BF00CF3C4A /* handmade_sim_region.cpp */, 200 | E0F04AFA1C5326BF00CF3C4A /* handmade_sim_region.h */, 201 | E0F04AFB1C5326BF00CF3C4A /* handmade_world.cpp */, 202 | E0F04AFC1C5326BF00CF3C4A /* handmade_world.h */, 203 | E0F04AFD1C5326BF00CF3C4A /* linux_handmade.cpp */, 204 | E0F04AFE1C5326BF00CF3C4A /* static_check.bat */, 205 | E0F04AFF1C5326BF00CF3C4A /* todo_check.bat */, 206 | E0F04B001C5326BF00CF3C4A /* win32_handmade.cpp */, 207 | E0F04B011C5326BF00CF3C4A /* win32_handmade.h */, 208 | ); 209 | path = code; 210 | sourceTree = ""; 211 | }; 212 | E0F04B021C5326BF00CF3C4A /* data */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | E0F04B031C5326BF00CF3C4A /* test */, 216 | E0F04B171C5326BF00CF3C4A /* test2 */, 217 | E0F04B281C5326BF00CF3C4A /* test3 */, 218 | ); 219 | path = data; 220 | sourceTree = ""; 221 | }; 222 | E0F04B031C5326BF00CF3C4A /* test */ = { 223 | isa = PBXGroup; 224 | children = ( 225 | E0F04B041C5326BF00CF3C4A /* test_background.bmp */, 226 | E0F04B051C5326BF00CF3C4A /* test_hero_back_cape.bmp */, 227 | E0F04B061C5326BF00CF3C4A /* test_hero_back_head.bmp */, 228 | E0F04B071C5326BF00CF3C4A /* test_hero_back_torso.bmp */, 229 | E0F04B081C5326BF00CF3C4A /* test_hero_front_cape.bmp */, 230 | E0F04B091C5326BF00CF3C4A /* test_hero_front_head.bmp */, 231 | E0F04B0A1C5326BF00CF3C4A /* test_hero_front_torso.bmp */, 232 | E0F04B0B1C5326BF00CF3C4A /* test_hero_left_cape.bmp */, 233 | E0F04B0C1C5326BF00CF3C4A /* test_hero_left_head.bmp */, 234 | E0F04B0D1C5326BF00CF3C4A /* test_hero_left_torso.bmp */, 235 | E0F04B0E1C5326BF00CF3C4A /* test_hero_right_cape.bmp */, 236 | E0F04B0F1C5326BF00CF3C4A /* test_hero_right_head.bmp */, 237 | E0F04B101C5326BF00CF3C4A /* test_hero_right_torso.bmp */, 238 | E0F04B111C5326BF00CF3C4A /* test_hero_shadow.bmp */, 239 | E0F04B121C5326BF00CF3C4A /* test_scene_layer_00.bmp */, 240 | E0F04B131C5326BF00CF3C4A /* test_scene_layer_01.bmp */, 241 | E0F04B141C5326BF00CF3C4A /* test_scene_layer_02.bmp */, 242 | E0F04B151C5326BF00CF3C4A /* test_scene_layer_03.bmp */, 243 | E0F04B161C5326BF00CF3C4A /* test_scene_layer_04.bmp */, 244 | ); 245 | path = test; 246 | sourceTree = ""; 247 | }; 248 | E0F04B171C5326BF00CF3C4A /* test2 */ = { 249 | isa = PBXGroup; 250 | children = ( 251 | E0F04B181C5326BF00CF3C4A /* grass00.bmp */, 252 | E0F04B191C5326BF00CF3C4A /* grass01.bmp */, 253 | E0F04B1A1C5326BF00CF3C4A /* ground00.bmp */, 254 | E0F04B1B1C5326BF00CF3C4A /* ground01.bmp */, 255 | E0F04B1C1C5326BF00CF3C4A /* ground02.bmp */, 256 | E0F04B1D1C5326BF00CF3C4A /* ground03.bmp */, 257 | E0F04B1E1C5326BF00CF3C4A /* rock00.bmp */, 258 | E0F04B1F1C5326BF00CF3C4A /* rock01.bmp */, 259 | E0F04B201C5326BF00CF3C4A /* rock02.bmp */, 260 | E0F04B211C5326BF00CF3C4A /* rock03.bmp */, 261 | E0F04B221C5326BF00CF3C4A /* tree00.bmp */, 262 | E0F04B231C5326BF00CF3C4A /* tree01.bmp */, 263 | E0F04B241C5326BF00CF3C4A /* tree02.bmp */, 264 | E0F04B251C5326BF00CF3C4A /* tuft00.bmp */, 265 | E0F04B261C5326BF00CF3C4A /* tuft01.bmp */, 266 | E0F04B271C5326BF00CF3C4A /* tuft02.bmp */, 267 | ); 268 | path = test2; 269 | sourceTree = ""; 270 | }; 271 | E0F04B281C5326BF00CF3C4A /* test3 */ = { 272 | isa = PBXGroup; 273 | children = ( 274 | E0F04B291C5326BF00CF3C4A /* bloop_00.wav */, 275 | E0F04B2A1C5326BF00CF3C4A /* bloop_01.wav */, 276 | E0F04B2B1C5326BF00CF3C4A /* bloop_02.wav */, 277 | E0F04B2C1C5326BF00CF3C4A /* bloop_03.wav */, 278 | E0F04B2D1C5326BF00CF3C4A /* bloop_04.wav */, 279 | E0F04B2E1C5326BF00CF3C4A /* crack_00.wav */, 280 | E0F04B2F1C5326BF00CF3C4A /* drop_00.wav */, 281 | E0F04B301C5326BF00CF3C4A /* glide_00.wav */, 282 | E0F04B311C5326BF00CF3C4A /* music_test.wav */, 283 | E0F04B321C5326BF00CF3C4A /* puhp_00.wav */, 284 | E0F04B331C5326BF00CF3C4A /* puhp_01.wav */, 285 | ); 286 | path = test3; 287 | sourceTree = ""; 288 | }; 289 | E0F04B361C5326BF00CF3C4A /* misc */ = { 290 | isa = PBXGroup; 291 | children = ( 292 | E0F04B371C5326BF00CF3C4A /* .emacs */, 293 | E0F04B381C5326BF00CF3C4A /* emacs.bat */, 294 | E0F04B391C5326BF00CF3C4A /* emacs_keybindings.txt */, 295 | E0F04B3A1C5326BF00CF3C4A /* handmade_hero_settings_msvc2013.vssettings */, 296 | E0F04B3B1C5326BF00CF3C4A /* shell.bat */, 297 | ); 298 | path = misc; 299 | sourceTree = ""; 300 | }; 301 | E0F3C4391A82E85100A6D5EF = { 302 | isa = PBXGroup; 303 | children = ( 304 | E0AC989A1A96C6BB009C64F9 /* config.xcconfig */, 305 | E0B1F8361A858DFE00BFD1AC /* AudioToolbox.framework */, 306 | E0B1F8341A858DF400BFD1AC /* CoreGraphics.framework */, 307 | E0B1F8321A858DE900BFD1AC /* UIKit.framework */, 308 | E0F04AED1C5326BF00CF3C4A /* handmade */, 309 | E0F3C4441A82E85200A6D5EF /* ios_handmade */, 310 | E0F3C4431A82E85200A6D5EF /* Products */, 311 | ); 312 | sourceTree = ""; 313 | }; 314 | E0F3C4431A82E85200A6D5EF /* Products */ = { 315 | isa = PBXGroup; 316 | children = ( 317 | E0F3C4421A82E85200A6D5EF /* ios_handmade.app */, 318 | ); 319 | name = Products; 320 | sourceTree = ""; 321 | }; 322 | E0F3C4441A82E85200A6D5EF /* ios_handmade */ = { 323 | isa = PBXGroup; 324 | children = ( 325 | E0F3C4461A82E85200A6D5EF /* Info.plist */, 326 | E0F3C4471A82E85200A6D5EF /* ios_main.m */, 327 | E0F3C4491A82E85200A6D5EF /* ios_app_delegate.h */, 328 | E0F3C44A1A82E85200A6D5EF /* ios_app_delegate.m */, 329 | E0F3C4561A82E85200A6D5EF /* Images.xcassets */, 330 | ); 331 | path = ios_handmade; 332 | sourceTree = ""; 333 | }; 334 | /* End PBXGroup section */ 335 | 336 | /* Begin PBXHeadersBuildPhase section */ 337 | E04D05741A86BF1600BBE114 /* Headers */ = { 338 | isa = PBXHeadersBuildPhase; 339 | buildActionMask = 2147483647; 340 | files = ( 341 | E0F04B441C5326BF00CF3C4A /* handmade_math.h in Headers */, 342 | E0F04B461C5326BF00CF3C4A /* handmade_random.h in Headers */, 343 | E0F04B431C5326BF00CF3C4A /* handmade_intrinsics.h in Headers */, 344 | E0F04B481C5326BF00CF3C4A /* handmade_sim_region.h in Headers */, 345 | E0F04B4F1C5326BF00CF3C4A /* win32_handmade.h in Headers */, 346 | E0F04B401C5326BF00CF3C4A /* handmade.h in Headers */, 347 | E0F04B421C5326BF00CF3C4A /* handmade_entity.h in Headers */, 348 | E0F04B451C5326BF00CF3C4A /* handmade_platform.h in Headers */, 349 | E0F04B4A1C5326BF00CF3C4A /* handmade_world.h in Headers */, 350 | ); 351 | runOnlyForDeploymentPostprocessing = 0; 352 | }; 353 | /* End PBXHeadersBuildPhase section */ 354 | 355 | /* Begin PBXNativeTarget section */ 356 | E0F3C4411A82E85200A6D5EF /* ios_handmade */ = { 357 | isa = PBXNativeTarget; 358 | buildConfigurationList = E0F3C4691A82E85200A6D5EF /* Build configuration list for PBXNativeTarget "ios_handmade" */; 359 | buildPhases = ( 360 | E0F3C43E1A82E85200A6D5EF /* Sources */, 361 | E0F3C43F1A82E85200A6D5EF /* Frameworks */, 362 | E0F3C4401A82E85200A6D5EF /* Resources */, 363 | E04D05741A86BF1600BBE114 /* Headers */, 364 | ); 365 | buildRules = ( 366 | ); 367 | dependencies = ( 368 | ); 369 | name = ios_handmade; 370 | productName = ios_handmade; 371 | productReference = E0F3C4421A82E85200A6D5EF /* ios_handmade.app */; 372 | productType = "com.apple.product-type.application"; 373 | }; 374 | /* End PBXNativeTarget section */ 375 | 376 | /* Begin PBXProject section */ 377 | E0F3C43A1A82E85100A6D5EF /* Project object */ = { 378 | isa = PBXProject; 379 | attributes = { 380 | LastUpgradeCheck = 0610; 381 | ORGANIZATIONNAME = "Zachary O'Keefe"; 382 | TargetAttributes = { 383 | E0F3C4411A82E85200A6D5EF = { 384 | CreatedOnToolsVersion = 6.1.1; 385 | }; 386 | }; 387 | }; 388 | buildConfigurationList = E0F3C43D1A82E85200A6D5EF /* Build configuration list for PBXProject "ios_handmade" */; 389 | compatibilityVersion = "Xcode 3.2"; 390 | developmentRegion = English; 391 | hasScannedForEncodings = 0; 392 | knownRegions = ( 393 | en, 394 | Base, 395 | ); 396 | mainGroup = E0F3C4391A82E85100A6D5EF; 397 | productRefGroup = E0F3C4431A82E85200A6D5EF /* Products */; 398 | projectDirPath = ""; 399 | projectRoot = ""; 400 | targets = ( 401 | E0F3C4411A82E85200A6D5EF /* ios_handmade */, 402 | ); 403 | }; 404 | /* End PBXProject section */ 405 | 406 | /* Begin PBXResourcesBuildPhase section */ 407 | E0F3C4401A82E85200A6D5EF /* Resources */ = { 408 | isa = PBXResourcesBuildPhase; 409 | buildActionMask = 2147483647; 410 | files = ( 411 | E0F04B861C53270900CF3C4A /* test_background.bmp in Resources */, 412 | E0F04B871C53270900CF3C4A /* test_hero_back_cape.bmp in Resources */, 413 | E0F04B881C53270900CF3C4A /* test_hero_back_head.bmp in Resources */, 414 | E0F04B891C53270900CF3C4A /* test_hero_back_torso.bmp in Resources */, 415 | E0F04B8A1C53270900CF3C4A /* test_hero_front_cape.bmp in Resources */, 416 | E0F04B8B1C53270900CF3C4A /* test_hero_front_head.bmp in Resources */, 417 | E0F04B8C1C53270900CF3C4A /* test_hero_front_torso.bmp in Resources */, 418 | E0F04B8D1C53270900CF3C4A /* test_hero_left_cape.bmp in Resources */, 419 | E0F04B8E1C53270900CF3C4A /* test_hero_left_head.bmp in Resources */, 420 | E0F04B8F1C53270900CF3C4A /* test_hero_left_torso.bmp in Resources */, 421 | E0F04B901C53270900CF3C4A /* test_hero_right_cape.bmp in Resources */, 422 | E0F04B911C53270900CF3C4A /* test_hero_right_head.bmp in Resources */, 423 | E0F04B921C53270900CF3C4A /* test_hero_right_torso.bmp in Resources */, 424 | E0F04B931C53270900CF3C4A /* test_hero_shadow.bmp in Resources */, 425 | E0F04B941C53270900CF3C4A /* test_scene_layer_00.bmp in Resources */, 426 | E0F04B951C53270900CF3C4A /* test_scene_layer_01.bmp in Resources */, 427 | E0F04B961C53270900CF3C4A /* test_scene_layer_02.bmp in Resources */, 428 | E0F04B971C53270900CF3C4A /* test_scene_layer_03.bmp in Resources */, 429 | E0F04B981C53270900CF3C4A /* test_scene_layer_04.bmp in Resources */, 430 | E0F04B991C53270900CF3C4A /* grass00.bmp in Resources */, 431 | E0F04B9A1C53270900CF3C4A /* grass01.bmp in Resources */, 432 | E0F04B9B1C53270900CF3C4A /* ground00.bmp in Resources */, 433 | E0F04B9C1C53270900CF3C4A /* ground01.bmp in Resources */, 434 | E0F04B9D1C53270900CF3C4A /* ground02.bmp in Resources */, 435 | E0F04B9E1C53270900CF3C4A /* ground03.bmp in Resources */, 436 | E0F04B9F1C53270900CF3C4A /* rock00.bmp in Resources */, 437 | E0F04BA01C53270900CF3C4A /* rock01.bmp in Resources */, 438 | E0F04BA11C53270900CF3C4A /* rock02.bmp in Resources */, 439 | E0F04BA21C53270900CF3C4A /* rock03.bmp in Resources */, 440 | E0F04BA31C53270900CF3C4A /* tree00.bmp in Resources */, 441 | E0F04BA41C53270900CF3C4A /* tree01.bmp in Resources */, 442 | E0F04BA51C53270900CF3C4A /* tree02.bmp in Resources */, 443 | E0F04BA61C53270900CF3C4A /* tuft00.bmp in Resources */, 444 | E0F04BA71C53270900CF3C4A /* tuft01.bmp in Resources */, 445 | E0F04BA81C53270900CF3C4A /* tuft02.bmp in Resources */, 446 | E0F04BA91C53270900CF3C4A /* bloop_00.wav in Resources */, 447 | E0F04BAA1C53270900CF3C4A /* bloop_01.wav in Resources */, 448 | E0F04BAB1C53270900CF3C4A /* bloop_02.wav in Resources */, 449 | E0F04BAC1C53270900CF3C4A /* bloop_03.wav in Resources */, 450 | E0F04BAD1C53270900CF3C4A /* bloop_04.wav in Resources */, 451 | E0F04BAE1C53270900CF3C4A /* crack_00.wav in Resources */, 452 | E0F04BAF1C53270900CF3C4A /* drop_00.wav in Resources */, 453 | E0F04BB01C53270900CF3C4A /* glide_00.wav in Resources */, 454 | E0F04BB11C53270900CF3C4A /* music_test.wav in Resources */, 455 | E0F04BB21C53270900CF3C4A /* puhp_00.wav in Resources */, 456 | E0F04BB31C53270900CF3C4A /* puhp_01.wav in Resources */, 457 | ); 458 | runOnlyForDeploymentPostprocessing = 0; 459 | }; 460 | /* End PBXResourcesBuildPhase section */ 461 | 462 | /* Begin PBXSourcesBuildPhase section */ 463 | E0F3C43E1A82E85200A6D5EF /* Sources */ = { 464 | isa = PBXSourcesBuildPhase; 465 | buildActionMask = 2147483647; 466 | files = ( 467 | E0F04B3F1C5326BF00CF3C4A /* handmade.cpp in Sources */, 468 | E0F3C44B1A82E85200A6D5EF /* ios_app_delegate.m in Sources */, 469 | E0F3C4481A82E85200A6D5EF /* ios_main.m in Sources */, 470 | ); 471 | runOnlyForDeploymentPostprocessing = 0; 472 | }; 473 | /* End PBXSourcesBuildPhase section */ 474 | 475 | /* Begin XCBuildConfiguration section */ 476 | E0F3C4671A82E85200A6D5EF /* debug */ = { 477 | isa = XCBuildConfiguration; 478 | buildSettings = { 479 | ALWAYS_SEARCH_USER_PATHS = NO; 480 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 481 | CLANG_CXX_LIBRARY = "libc++"; 482 | CLANG_ENABLE_MODULES = YES; 483 | CLANG_ENABLE_OBJC_ARC = YES; 484 | CLANG_WARN_BOOL_CONVERSION = YES; 485 | CLANG_WARN_CONSTANT_CONVERSION = YES; 486 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 487 | CLANG_WARN_EMPTY_BODY = YES; 488 | CLANG_WARN_ENUM_CONVERSION = YES; 489 | CLANG_WARN_INT_CONVERSION = YES; 490 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 491 | CLANG_WARN_UNREACHABLE_CODE = YES; 492 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 493 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 494 | COPY_PHASE_STRIP = NO; 495 | ENABLE_STRICT_OBJC_MSGSEND = YES; 496 | GCC_C_LANGUAGE_STANDARD = gnu99; 497 | GCC_DYNAMIC_NO_PIC = NO; 498 | GCC_OPTIMIZATION_LEVEL = 0; 499 | GCC_PREPROCESSOR_DEFINITIONS = ( 500 | "DEBUG=1", 501 | "$(inherited)", 502 | ); 503 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 504 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 505 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 506 | GCC_WARN_UNDECLARED_SELECTOR = YES; 507 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 508 | GCC_WARN_UNUSED_FUNCTION = YES; 509 | GCC_WARN_UNUSED_VARIABLE = YES; 510 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 511 | MTL_ENABLE_DEBUG_INFO = YES; 512 | ONLY_ACTIVE_ARCH = YES; 513 | SDKROOT = iphoneos; 514 | TARGETED_DEVICE_FAMILY = "1,2"; 515 | }; 516 | name = debug; 517 | }; 518 | E0F3C4681A82E85200A6D5EF /* release */ = { 519 | isa = XCBuildConfiguration; 520 | buildSettings = { 521 | ALWAYS_SEARCH_USER_PATHS = NO; 522 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 523 | CLANG_CXX_LIBRARY = "libc++"; 524 | CLANG_ENABLE_MODULES = YES; 525 | CLANG_ENABLE_OBJC_ARC = YES; 526 | CLANG_WARN_BOOL_CONVERSION = YES; 527 | CLANG_WARN_CONSTANT_CONVERSION = YES; 528 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 529 | CLANG_WARN_EMPTY_BODY = YES; 530 | CLANG_WARN_ENUM_CONVERSION = YES; 531 | CLANG_WARN_INT_CONVERSION = YES; 532 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 533 | CLANG_WARN_UNREACHABLE_CODE = YES; 534 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 535 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 536 | COPY_PHASE_STRIP = YES; 537 | ENABLE_NS_ASSERTIONS = NO; 538 | ENABLE_STRICT_OBJC_MSGSEND = YES; 539 | GCC_C_LANGUAGE_STANDARD = gnu99; 540 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 541 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 542 | GCC_WARN_UNDECLARED_SELECTOR = YES; 543 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 544 | GCC_WARN_UNUSED_FUNCTION = YES; 545 | GCC_WARN_UNUSED_VARIABLE = YES; 546 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 547 | MTL_ENABLE_DEBUG_INFO = NO; 548 | SDKROOT = iphoneos; 549 | TARGETED_DEVICE_FAMILY = "1,2"; 550 | VALIDATE_PRODUCT = YES; 551 | }; 552 | name = release; 553 | }; 554 | E0F3C46A1A82E85200A6D5EF /* debug */ = { 555 | isa = XCBuildConfiguration; 556 | baseConfigurationReference = E0AC989A1A96C6BB009C64F9 /* config.xcconfig */; 557 | buildSettings = { 558 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 559 | CFLAGS = ""; 560 | CLANG_ENABLE_MODULES = YES; 561 | CLANG_ENABLE_OBJC_ARC = NO; 562 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 563 | INFOPLIST_FILE = ios_handmade/Info.plist; 564 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 565 | PRODUCT_NAME = "$(TARGET_NAME)"; 566 | TARGETED_DEVICE_FAMILY = "1,2"; 567 | }; 568 | name = debug; 569 | }; 570 | E0F3C46B1A82E85200A6D5EF /* release */ = { 571 | isa = XCBuildConfiguration; 572 | buildSettings = { 573 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 574 | CLANG_ENABLE_MODULES = YES; 575 | CLANG_ENABLE_OBJC_ARC = NO; 576 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 577 | INFOPLIST_FILE = ios_handmade/Info.plist; 578 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 579 | PRODUCT_NAME = "$(TARGET_NAME)"; 580 | TARGETED_DEVICE_FAMILY = "1,2"; 581 | }; 582 | name = release; 583 | }; 584 | /* End XCBuildConfiguration section */ 585 | 586 | /* Begin XCConfigurationList section */ 587 | E0F3C43D1A82E85200A6D5EF /* Build configuration list for PBXProject "ios_handmade" */ = { 588 | isa = XCConfigurationList; 589 | buildConfigurations = ( 590 | E0F3C4671A82E85200A6D5EF /* debug */, 591 | E0F3C4681A82E85200A6D5EF /* release */, 592 | ); 593 | defaultConfigurationIsVisible = 0; 594 | defaultConfigurationName = debug; 595 | }; 596 | E0F3C4691A82E85200A6D5EF /* Build configuration list for PBXNativeTarget "ios_handmade" */ = { 597 | isa = XCConfigurationList; 598 | buildConfigurations = ( 599 | E0F3C46A1A82E85200A6D5EF /* debug */, 600 | E0F3C46B1A82E85200A6D5EF /* release */, 601 | ); 602 | defaultConfigurationIsVisible = 0; 603 | defaultConfigurationName = debug; 604 | }; 605 | /* End XCConfigurationList section */ 606 | }; 607 | rootObject = E0F3C43A1A82E85100A6D5EF /* Project object */; 608 | } 609 | -------------------------------------------------------------------------------- /ios_handmade.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios_handmade.xcodeproj/project.xcworkspace/xcshareddata/ios_handmade.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "FAD3A05B57EE9CC41D8ED9626A777327D296939F", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "FAD3A05B57EE9CC41D8ED9626A777327D296939F" : 0, 8 | "0EE183645467C08F1B1C4F9125008A359A660222" : 0 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "225434E3-94A4-4411-B28F-E96D0D17D896", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "FAD3A05B57EE9CC41D8ED9626A777327D296939F" : "ios_handmade", 13 | "0EE183645467C08F1B1C4F9125008A359A660222" : "ios_handmade\/handmade\/" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "ios_handmade", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "ios_handmade.xcodeproj", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/HandmadeHero\/cpp.git", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "0EE183645467C08F1B1C4F9125008A359A660222" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/zokeefe\/ios_handmade.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "FAD3A05B57EE9CC41D8ED9626A777327D296939F" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /ios_handmade/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /ios_handmade/Images.xcassets/launch_image.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "8.0", 8 | "subtype" : "736h", 9 | "scale" : "3x" 10 | }, 11 | { 12 | "orientation" : "landscape", 13 | "idiom" : "iphone", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "8.0", 16 | "subtype" : "736h", 17 | "scale" : "3x" 18 | }, 19 | { 20 | "orientation" : "portrait", 21 | "idiom" : "iphone", 22 | "extent" : "full-screen", 23 | "minimum-system-version" : "8.0", 24 | "subtype" : "667h", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "orientation" : "portrait", 29 | "idiom" : "iphone", 30 | "extent" : "full-screen", 31 | "minimum-system-version" : "7.0", 32 | "scale" : "2x" 33 | }, 34 | { 35 | "orientation" : "portrait", 36 | "idiom" : "iphone", 37 | "extent" : "full-screen", 38 | "minimum-system-version" : "7.0", 39 | "subtype" : "retina4", 40 | "scale" : "2x" 41 | }, 42 | { 43 | "orientation" : "portrait", 44 | "idiom" : "ipad", 45 | "extent" : "full-screen", 46 | "minimum-system-version" : "7.0", 47 | "scale" : "1x" 48 | }, 49 | { 50 | "orientation" : "landscape", 51 | "idiom" : "ipad", 52 | "extent" : "full-screen", 53 | "minimum-system-version" : "7.0", 54 | "scale" : "1x" 55 | }, 56 | { 57 | "orientation" : "portrait", 58 | "idiom" : "ipad", 59 | "extent" : "full-screen", 60 | "minimum-system-version" : "7.0", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "orientation" : "landscape", 65 | "idiom" : "ipad", 66 | "extent" : "full-screen", 67 | "minimum-system-version" : "7.0", 68 | "scale" : "2x" 69 | }, 70 | { 71 | "orientation" : "portrait", 72 | "idiom" : "iphone", 73 | "extent" : "full-screen", 74 | "scale" : "1x" 75 | }, 76 | { 77 | "orientation" : "portrait", 78 | "idiom" : "iphone", 79 | "extent" : "full-screen", 80 | "scale" : "2x" 81 | }, 82 | { 83 | "orientation" : "portrait", 84 | "idiom" : "iphone", 85 | "extent" : "full-screen", 86 | "subtype" : "retina4", 87 | "scale" : "2x" 88 | }, 89 | { 90 | "orientation" : "portrait", 91 | "idiom" : "ipad", 92 | "extent" : "to-status-bar", 93 | "scale" : "1x" 94 | }, 95 | { 96 | "orientation" : "portrait", 97 | "idiom" : "ipad", 98 | "extent" : "full-screen", 99 | "scale" : "1x" 100 | }, 101 | { 102 | "orientation" : "landscape", 103 | "idiom" : "ipad", 104 | "extent" : "to-status-bar", 105 | "scale" : "1x" 106 | }, 107 | { 108 | "orientation" : "landscape", 109 | "idiom" : "ipad", 110 | "extent" : "full-screen", 111 | "scale" : "1x" 112 | }, 113 | { 114 | "orientation" : "portrait", 115 | "idiom" : "ipad", 116 | "extent" : "to-status-bar", 117 | "scale" : "2x" 118 | }, 119 | { 120 | "orientation" : "portrait", 121 | "idiom" : "ipad", 122 | "extent" : "full-screen", 123 | "scale" : "2x" 124 | }, 125 | { 126 | "orientation" : "landscape", 127 | "idiom" : "ipad", 128 | "extent" : "to-status-bar", 129 | "scale" : "2x" 130 | }, 131 | { 132 | "orientation" : "landscape", 133 | "idiom" : "ipad", 134 | "extent" : "full-screen", 135 | "scale" : "2x" 136 | } 137 | ], 138 | "info" : { 139 | "version" : 1, 140 | "author" : "xcode" 141 | } 142 | } -------------------------------------------------------------------------------- /ios_handmade/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | zokeefe.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | launch_image 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarHidden 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /ios_handmade/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pushd ../ 4 | 5 | IS_SIMULATOR=1 6 | 7 | #NAME='iPhone 4s' 8 | #NAME='iPhone 5' 9 | #NAME='iPhone 5s' 10 | NAME="iPhone 6" 11 | #NAME='iPhone 6 Plus' 12 | #NAME='iPad 2' 13 | #NAME='iPad Air' 14 | #NAME='iPad Retina' 15 | 16 | DEVICE_ID=0 17 | 18 | if [ "$IS_SIMULATOR" -ne 0 ]; then 19 | #DEST="'platform=iOS Simulator,name=$NAME,OS=latest'" 20 | DEST="platform=iOS Simulator,name=$NAME,OS=latest" 21 | SDK='iphonesimulator9.2' 22 | else 23 | DEST='platform=iOS,name='$NAME',id='$DEVICE_ID 24 | SDK='iphoneos8.1' 25 | fi 26 | 27 | #eval "xcodebuild \ 28 | # -destination $DEST \ 29 | # -configuration debug \ 30 | # -sdk $SDK build" 31 | 32 | xcodebuild \ 33 | -destination "$DEST" \ 34 | -configuration debug \ 35 | -sdk $SDK \ 36 | build 37 | 38 | popd 39 | -------------------------------------------------------------------------------- /ios_handmade/ios_app_delegate.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef IOS_APP_DELEGATE_H 3 | #define IOS_APP_DELEGATE_H 4 | 5 | #import 6 | #import 7 | #import 8 | #import "../handmade/code/handmade_platform.h" 9 | 10 | // TODO(zach): Make this error proof. 11 | #define IOS_PATH_MAX PATH_MAX 12 | 13 | typedef enum { 14 | NONE = 0, 15 | RECORDING, 16 | PLAYBACK 17 | } replay_state; 18 | 19 | typedef struct { 20 | int samplesHz; 21 | size_t bytesPerChannelPerPacket; 22 | size_t bytesPerPacket; 23 | AudioComponentInstance audioUnit; 24 | } ios_sound_output; 25 | 26 | typedef struct { 27 | void *memory; 28 | int width; 29 | int height; 30 | int pitch; 31 | int bytesPerPixel; 32 | } ios_offscreen_buffer; 33 | 34 | typedef struct { 35 | int fd; 36 | char fileName[IOS_PATH_MAX]; 37 | void *memoryBlock; 38 | } ios_replay_buffer; 39 | 40 | typedef struct { 41 | size_t totalMemorySize; 42 | // NOTE(zach): Storage memory is permanent storage & transient storage 43 | size_t storageMemorySize; 44 | void *memory; 45 | void *storageMemory; 46 | char writableDataPath[IOS_PATH_MAX]; 47 | Float32 pointToPixelScale; 48 | ios_replay_buffer replayBuffer; 49 | replay_state replayState; 50 | int inputReplayFd; 51 | char replayInputPath[IOS_PATH_MAX]; 52 | } ios_state; 53 | 54 | typedef struct { 55 | Float32 centerX; 56 | Float32 centerY; 57 | Float32 radius; 58 | bool isDown; 59 | int halfTransitionCount; 60 | } ios_input_round_button; 61 | 62 | typedef struct { 63 | Float32 centerX; 64 | Float32 centerY; 65 | Float32 radius; 66 | // IMPORTANT(zach): Stick X,Y in range of [-1.0, 1.0] 67 | Float32 stickX; 68 | Float32 stickY; 69 | } ios_input_joystick; 70 | 71 | typedef struct { 72 | Float32 x; 73 | Float32 y; 74 | Float32 radius; 75 | UITouchPhase phase; 76 | } ios_input_touch; 77 | 78 | typedef struct { 79 | ios_input_joystick joystick; 80 | 81 | #if IOS_HANDMADE_DEBUG_INPUT 82 | union { 83 | ios_input_round_button debugButtons[1]; 84 | struct { 85 | ios_input_round_button debugLoop; 86 | }; 87 | }; 88 | #endif 89 | 90 | union { 91 | ios_input_round_button buttons[8]; 92 | struct { 93 | ios_input_round_button actionUp; 94 | ios_input_round_button actionDown; 95 | ios_input_round_button actionLeft; 96 | ios_input_round_button actionRight; 97 | 98 | ios_input_round_button leftShoulder; 99 | ios_input_round_button rightShoulder; 100 | 101 | ios_input_round_button back; 102 | ios_input_round_button start; 103 | }; 104 | }; 105 | } ios_input; 106 | 107 | // TODO(zach): Need whilst we don't load game code dynamically. 108 | GAME_UPDATE_AND_RENDER(GameUpdateAndRender); 109 | GAME_GET_SOUND_SAMPLES(GameGetSoundSamples); 110 | 111 | @interface App_delegate : UIResponder 112 | 113 | @property (strong, nonatomic) UIWindow *window; 114 | 115 | - (void)touchEvent:(UIEvent *)event; 116 | 117 | @end 118 | 119 | #endif 120 | 121 | -------------------------------------------------------------------------------- /ios_handmade/ios_app_delegate.m: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | #import 4 | #import 5 | #import 6 | #include 7 | #import 8 | #import 9 | #import 10 | #import 11 | #import 12 | #import 13 | #import "ios_app_delegate.h" 14 | #import "../handmade/code/handmade_platform.h" 15 | 16 | // NOTE(zach): I dont know if alternating buffers is of any benifit since I don't know how 17 | // Core Animation rendering works underneath. I won't worry about it since I'm switching to 18 | // OpenGL anyways. 19 | // 20 | // HACK(zach): Fixup these globals later 21 | global_variable ios_offscreen_buffer globalBackBuffers[2]; 22 | global_variable ios_sound_output globalSoundOutput; 23 | global_variable ios_state globalState; 24 | global_variable ios_input globalInput; 25 | global_variable ios_input_touch globalLastTouch; 26 | global_variable game_memory globalGameMemory; 27 | global_variable CADisplayLink *globalDisplayLink; 28 | 29 | 30 | internal void iosBuildFilePathUnchecked(const char *base, const char *append, char *dest) { 31 | const char *cptr = base; 32 | if (*base != '\0') { 33 | while ((*(dest++) = *(cptr++)) != '\0') ; 34 | *(dest - 1) = '/'; 35 | } 36 | cptr = append; 37 | while ((*(dest++) = *(cptr++)) != '\0') ; 38 | } 39 | 40 | #if HANDMADE_INTERNAL 41 | // NOTE(zach): Not for shipping. 42 | // 43 | // TODO(zach): Need to request change to downstream code to include amount of memory 44 | // requesting to be freed in DEBUG_PLATFORM_FREE_MEMORY implementation. 45 | // I imagine this will be the case in non-debug code. For now, use free(), and malloc() 46 | 47 | internal DEBUG_PLATFORM_FREE_FILE_MEMORY(debugPlatformFreeFileMemory) { 48 | if (Memory) { 49 | free(Memory); 50 | } 51 | } 52 | 53 | internal DEBUG_PLATFORM_READ_ENTIRE_FILE(debugPlatformReadEntireFile) { 54 | debug_read_file_result result = {0}; 55 | 56 | char path[IOS_PATH_MAX]; 57 | iosBuildFilePathUnchecked(globalState.writableDataPath, Filename, path); 58 | 59 | int fd; 60 | if ((fd = open(path, O_RDONLY)) != -1) { 61 | struct stat fileStat; 62 | if (fstat(fd, &fileStat) != -1) { 63 | size_t fileSize = (size_t)fileStat.st_size; 64 | if ((result.Contents = malloc(fileSize)) != NULL) { 65 | if (read(fd, result.Contents, fileSize) == fileSize) { 66 | result.ContentsSize = (uint32_t)fileSize; 67 | } else { 68 | debugPlatformFreeFileMemory(Thread, result.Contents); 69 | result.ContentsSize = 0; 70 | // TODO(zach): logging 71 | } 72 | } else { 73 | // TODO(zach): logging 74 | } 75 | } else { 76 | // TODO(zach): logging 77 | } 78 | close(fd); 79 | } else { 80 | // TODO(zach): logging 81 | } 82 | return result; 83 | } 84 | 85 | internal DEBUG_PLATFORM_WRITE_ENTIRE_FILE(debugPlatformWriteEntireFile) { 86 | bool result = false; 87 | 88 | int fd = open(Filename, O_WRONLY | O_CREAT, 0644); 89 | if (fd != -1) { 90 | ssize_t BytesWritten = write(fd, Memory, MemorySize); 91 | if (!(result = (BytesWritten == MemorySize))) { 92 | // TODO(zach): Logging 93 | } 94 | close(fd); 95 | } else { 96 | // TODO(zach): Logging 97 | } 98 | return (bool32)result; 99 | } 100 | 101 | #endif 102 | 103 | internal void renderTouch(CGContextRef context, ios_input_touch touch) { 104 | switch (touch.phase) { 105 | case UITouchPhaseBegan: 106 | case UITouchPhaseMoved: 107 | case UITouchPhaseStationary: 108 | CGContextSetRGBFillColor(context, 1, 1, 0, 0.5); 109 | Float32 radius = 20.0f; 110 | CGRect rect = CGRectMake( 111 | touch.x - radius, 112 | touch.y - radius, 113 | radius * 2, 114 | radius * 2 ); 115 | CGContextFillEllipseInRect(context, rect); 116 | break; 117 | case UITouchPhaseEnded: 118 | case UITouchPhaseCancelled: 119 | default: 120 | break; 121 | } 122 | } 123 | 124 | #if 0 125 | internal OSStatus renderSineWave( 126 | void *inRefCon, 127 | AudioUnitRenderActionFlags *ioActionFlags, 128 | const AudioTimeStamp *inTimeStamp, 129 | UInt32 inOutputBusNumber, 130 | UInt32 inNumberFrames, 131 | AudioBufferList *ioData ) { 132 | 133 | ios_sound_output *soundOutput = (ios_sound_output *)inRefCon; 134 | 135 | local_persist double theta = 0; 136 | const double amplitude = 10000.0; 137 | double thetaInc = 2.0 * M_PI * globalLastTouch.x / (float)soundOutput->samplesHz; 138 | 139 | uint32_t *buffer = (uint32_t *)ioData->mBuffers[0].mData; 140 | 141 | for (uint32_t sample = 0; sample < inNumberFrames; ++sample) { 142 | // Just to show writing to the left and right channel 143 | buffer[sample] = (uint16_t)(sinf(theta) * amplitude) | 144 | ((uint32_t)(sinf(theta) * amplitude) << 16); 145 | theta += thetaInc; 146 | theta -= (theta < 2.0 * M_PI) ? 0 : 2.0 * M_PI; 147 | } 148 | return noErr; 149 | } 150 | #endif 151 | 152 | internal OSStatus getInputSoundSamples( 153 | void *inRefCon, 154 | AudioUnitRenderActionFlags *ioActionFlags, 155 | const AudioTimeStamp *inTimeStamp, 156 | UInt32 inOutputBusNumber, 157 | UInt32 inNumberFrames, 158 | AudioBufferList *ioData ) { 159 | 160 | thread_context thread = {0}; 161 | game_sound_output_buffer gameSoundBuffer; 162 | gameSoundBuffer.SamplesPerSecond = ((ios_sound_output *)inRefCon)->samplesHz; 163 | gameSoundBuffer.SampleCount = inNumberFrames; 164 | gameSoundBuffer.Samples = ioData->mBuffers[0].mData; 165 | GameGetSoundSamples(&thread, &globalGameMemory, &gameSoundBuffer); 166 | return noErr; 167 | } 168 | 169 | internal OSStatus iosInitAudioUnit(ios_sound_output* soundOutput) { 170 | 171 | OSErr err; 172 | 173 | AudioComponentDescription outputDescr; 174 | outputDescr.componentType = kAudioUnitType_Output; 175 | outputDescr.componentSubType = kAudioUnitSubType_RemoteIO; 176 | outputDescr.componentManufacturer = kAudioUnitManufacturer_Apple; 177 | outputDescr.componentFlags = 0; 178 | outputDescr.componentFlagsMask = 0; 179 | 180 | AudioComponent defaultOutput = AudioComponentFindNext(NULL, &outputDescr); 181 | 182 | if ((err = AudioComponentInstanceNew(defaultOutput, &soundOutput->audioUnit)) != noErr) 183 | return err; 184 | 185 | AURenderCallbackStruct inputCb; 186 | //inputCb.inputProc = renderSineWave; //getInputSoundSamples; 187 | inputCb.inputProc = getInputSoundSamples; 188 | inputCb.inputProcRefCon = soundOutput; 189 | if ((err = AudioUnitSetProperty( 190 | soundOutput->audioUnit, 191 | kAudioUnitProperty_SetRenderCallback, 192 | kAudioUnitScope_Input, 193 | 0, 194 | &inputCb, 195 | sizeof(inputCb) )) != noErr) return err; 196 | 197 | // IMPORTANT(zach): PCM, stereo, 16bpc, integer samples, interleaved 198 | // Left channel is the lower 16 bits, right is the higher 16 bits. 199 | AudioStreamBasicDescription streamFormat = {0}; 200 | streamFormat.mSampleRate = soundOutput->samplesHz; 201 | streamFormat.mFormatID = kAudioFormatLinearPCM; 202 | streamFormat.mFormatFlags = 203 | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger; 204 | streamFormat.mBytesPerPacket = (uint32_t)soundOutput->bytesPerPacket; 205 | streamFormat.mFramesPerPacket = 1; 206 | streamFormat.mBytesPerFrame = (uint32_t)soundOutput->bytesPerPacket; 207 | streamFormat.mChannelsPerFrame = 2; 208 | streamFormat.mBitsPerChannel = (uint32_t)soundOutput->bytesPerChannelPerPacket * 8; 209 | err = AudioUnitSetProperty ( 210 | soundOutput->audioUnit, 211 | kAudioUnitProperty_StreamFormat, 212 | kAudioUnitScope_Input, 213 | 0, 214 | &streamFormat, 215 | sizeof(AudioStreamBasicDescription) ); 216 | return err; 217 | } 218 | 219 | internal NSError *iosLoadWritableBundleData(NSString *sourcePath, NSString *destPath) { 220 | NSFileManager *fs = [NSFileManager defaultManager]; 221 | NSError *error = NULL; 222 | BOOL destIsDir; 223 | BOOL destExists = [fs fileExistsAtPath:destPath isDirectory:&destIsDir]; 224 | if (!destExists || !destIsDir) { 225 | if (![fs createDirectoryAtPath:destPath withIntermediateDirectories:TRUE attributes:nil error:&error]) { 226 | return error; 227 | } 228 | } 229 | NSArray *contents = [fs contentsOfDirectoryAtPath:sourcePath error:&error]; 230 | if (error) 231 | return error; 232 | 233 | for (NSString *obj in contents) { 234 | NSString *objDestPath = [destPath stringByAppendingPathComponent:obj]; 235 | if ([fs fileExistsAtPath: objDestPath]) { 236 | #if HANDMADE_INTERNAL 237 | if (![fs removeItemAtPath: objDestPath error:&error]) { 238 | return error; 239 | } 240 | #else 241 | continue; 242 | #endif 243 | } 244 | if (![fs copyItemAtPath: [sourcePath stringByAppendingPathComponent:obj] 245 | toPath: objDestPath error:&error] ) { 246 | return error; 247 | } else { 248 | NSLog(@"Loaded: %@\n", objDestPath); 249 | } 250 | } 251 | return error; 252 | } 253 | 254 | internal void iosInitInput(ios_input *input, ios_offscreen_buffer *buffer) { 255 | 256 | #define ASSERT_BOUNDS(x, y, r) Assert((x) - (r) >= 0) \ 257 | Assert((x) + (r) < buffer->width) \ 258 | Assert((y) - (r) >= 0) \ 259 | Assert((y) + (r) < buffer->height) 260 | 261 | 262 | // 263 | // Action buttons 264 | // 265 | const Float32 actionButtonRadius = 45.0; 266 | const Float32 actionButtonGroupCenterOffsetX = (Float32)buffer->width - 175.0; 267 | const Float32 actionButtonGroupCenterOffsetY = 175.0; 268 | const Float32 actionButtonGroupSpreadX = 90.0; 269 | const Float32 actionButtonGroupSpreadY = 90.0; 270 | 271 | input->actionUp.radius = input->actionDown.radius = input->actionLeft.radius = 272 | input->actionRight.radius = actionButtonRadius; 273 | 274 | input->actionUp.centerX = actionButtonGroupCenterOffsetX; 275 | input->actionUp.centerY = actionButtonGroupCenterOffsetY + actionButtonGroupSpreadY; 276 | ASSERT_BOUNDS(input->actionUp.centerX, input->actionUp.centerY, input->actionUp.radius) 277 | 278 | input->actionDown.centerX = actionButtonGroupCenterOffsetX; 279 | input->actionDown.centerY = actionButtonGroupCenterOffsetY - actionButtonGroupSpreadY; 280 | ASSERT_BOUNDS(input->actionDown.centerX, input->actionDown.centerY, input->actionDown.radius) 281 | 282 | input->actionLeft.centerX = actionButtonGroupCenterOffsetX - actionButtonGroupSpreadX; 283 | input->actionLeft.centerY = actionButtonGroupCenterOffsetY; 284 | ASSERT_BOUNDS(input->actionLeft.centerX, input->actionLeft.centerY, input->actionLeft.radius) 285 | 286 | input->actionRight.centerX = actionButtonGroupCenterOffsetX + actionButtonGroupSpreadX; 287 | input->actionRight.centerY = actionButtonGroupCenterOffsetY; 288 | ASSERT_BOUNDS(input->actionRight.centerX, input->actionRight.centerY, input->actionRight.radius) 289 | 290 | // 291 | // Joystick 292 | // 293 | input->joystick.centerX = 175.0; 294 | input->joystick.centerY = 175.0; 295 | input->joystick.radius = 100.0; 296 | ASSERT_BOUNDS(input->joystick.centerX, input->joystick.centerY, input->joystick.radius); 297 | 298 | // 299 | // Start/back 300 | // 301 | const Float32 ctrlButtonRadius = 30.0; 302 | const Float32 ctrlButtonGroupCenterOffsetY = buffer->height - 80.0; 303 | const Float32 ctrlButtonGroupSpreadX = 30.0; 304 | 305 | input->back.radius = input->start.radius = ctrlButtonRadius; 306 | input->back.centerY = input->start.centerY = ctrlButtonGroupCenterOffsetY; 307 | 308 | input->back.centerX = ctrlButtonGroupSpreadX + ctrlButtonRadius; 309 | ASSERT_BOUNDS(input->back.centerX, input->back.centerY, input->back.radius); 310 | 311 | input->start.centerX = (ctrlButtonGroupSpreadX * 2) + (ctrlButtonRadius * 3); 312 | ASSERT_BOUNDS(input->start.centerX, input->start.centerY, input->start.radius); 313 | 314 | #if IOS_HANDMADE_DEBUG_INPUT 315 | const Float32 debugButtonRadius = 45.0; 316 | const Float32 debugButtonGroupCenterOffsetY = buffer->height - 80.0; 317 | const Float32 debugButtonGroupSpreadX = 45.0; 318 | for (size_t i = 0; i < ArrayCount(input->debugButtons); ++i) { 319 | input->debugButtons[i].radius = debugButtonRadius; 320 | input->debugButtons[i].centerY = debugButtonGroupCenterOffsetY; 321 | input->debugButtons[i].centerX = buffer->width - debugButtonGroupSpreadX - 322 | debugButtonRadius - i * (debugButtonRadius * 2 + debugButtonGroupSpreadX); 323 | ASSERT_BOUNDS( 324 | input->debugButtons[i].centerX, 325 | input->debugButtons[i].centerY, 326 | input->debugButtons[i].radius ); 327 | } 328 | #endif 329 | } 330 | 331 | internal void iosRenderInputHud(CGContextRef context, ios_offscreen_buffer *buffer, ios_input *input) { 332 | CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 0.75); 333 | CGContextSetRGBStrokeColor(context, 0.0, 0.0, 1.0, 0.75); 334 | CGContextSetLineWidth(context, 3.0); 335 | for (size_t i = 0; i < ArrayCount(input->buttons); ++i) { 336 | ios_input_round_button button = input->buttons[i]; 337 | CGRect rect = CGRectMake( 338 | button.centerX - button.radius, 339 | button.centerY - button.radius, 340 | button.radius * 2, 341 | button.radius * 2 ); 342 | if (button.isDown) 343 | CGContextFillEllipseInRect(context, rect); 344 | else 345 | CGContextStrokeEllipseInRect(context, rect); 346 | } 347 | CGRect rect = CGRectMake( 348 | input->joystick.centerX - input->joystick.radius, 349 | input->joystick.centerY - input->joystick.radius, 350 | input->joystick.radius * 2, 351 | input->joystick.radius * 2 ); 352 | CGContextStrokeEllipseInRect(context, rect); 353 | 354 | ios_input_joystick *joystick = &input->joystick; 355 | if (joystick->stickX != 0 || joystick->stickX != 0) { 356 | #define stickRadius 80.0 357 | Assert(stickRadius < joystick->radius) 358 | Float32 radiusDiff = joystick->radius - stickRadius; 359 | rect = CGRectMake( 360 | joystick->centerX + joystick->stickX * radiusDiff - stickRadius, 361 | joystick->centerY + joystick->stickY * radiusDiff - stickRadius, 362 | stickRadius * 2, 363 | stickRadius * 2 ); 364 | CGContextFillEllipseInRect(context, rect); 365 | } 366 | #if IOS_HANDMADE_DEBUG_INPUT 367 | CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 0.75); 368 | CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 0.75); 369 | CGContextSetLineWidth(context, 3.0); 370 | for (size_t i = 0; i < ArrayCount(input->debugButtons); ++i) { 371 | ios_input_round_button button = input->debugButtons[i]; 372 | CGRect rect = CGRectMake( 373 | button.centerX - button.radius, 374 | button.centerY - button.radius, 375 | button.radius * 2, 376 | button.radius * 2 ); 377 | if (button.isDown) 378 | CGContextFillEllipseInRect(context, rect); 379 | else 380 | CGContextStrokeEllipseInRect(context, rect); 381 | } 382 | #endif 383 | } 384 | 385 | internal void iosProccessRoundButtonInput(ios_input_round_button *button, ios_input_touch *touches, 386 | size_t numTouches) { 387 | ios_input_touch touch; 388 | bool buttonPress = false; 389 | for (size_t i = 0; !buttonPress && i < numTouches; ++i) { 390 | touch = touches[i]; 391 | Float32 dX = (button->centerX - touch.x); 392 | Float32 dY = (button->centerY - touch.y); 393 | Float32 distance = sqrt(dX * dX + dY * dY); 394 | if (distance - button->radius - touch.radius < 0) { 395 | switch (touch.phase) { 396 | case UITouchPhaseBegan: 397 | case UITouchPhaseMoved: 398 | case UITouchPhaseStationary: 399 | buttonPress = true; 400 | break; 401 | case UITouchPhaseEnded: 402 | case UITouchPhaseCancelled: 403 | default: 404 | break; 405 | } 406 | } 407 | } 408 | button->halfTransitionCount += button->isDown == buttonPress ? 0 : 1; 409 | button->isDown = buttonPress; 410 | } 411 | 412 | internal inline void mapButtonInputToGameInput(ios_input_round_button *iosButton, 413 | game_button_state *gameButton) { 414 | gameButton->HalfTransitionCount = iosButton->halfTransitionCount; 415 | gameButton->EndedDown = iosButton->isDown; 416 | } 417 | 418 | internal void mapInputToGame(ios_input *iosInput, game_input *gameInput) { 419 | game_controller_input *controller = &gameInput->Controllers[0]; 420 | controller->IsConnected = true; 421 | 422 | mapButtonInputToGameInput(&iosInput->actionUp, &controller->ActionUp); 423 | mapButtonInputToGameInput(&iosInput->actionDown, &controller->ActionDown); 424 | mapButtonInputToGameInput(&iosInput->actionLeft, &controller->ActionLeft); 425 | mapButtonInputToGameInput(&iosInput->actionRight, &controller->ActionRight); 426 | mapButtonInputToGameInput(&iosInput->leftShoulder, &controller->LeftShoulder); 427 | mapButtonInputToGameInput(&iosInput->rightShoulder, &controller->RightShoulder); 428 | mapButtonInputToGameInput(&iosInput->back, &controller->Back); 429 | mapButtonInputToGameInput(&iosInput->start, &controller->Start); 430 | controller->IsAnalog = true; 431 | controller->StickAverageX = iosInput->joystick.stickX; 432 | controller->StickAverageY = iosInput->joystick.stickY; 433 | } 434 | 435 | internal void iosBeginRecordingInput(ios_state *state) { 436 | kern_return_t result = vm_copy( 437 | (vm_map_t)mach_task_self(), 438 | (vm_address_t)state->storageMemory, 439 | (vm_size_t)state->storageMemorySize, 440 | (vm_address_t)state->replayBuffer.memoryBlock ); 441 | if (result == KERN_SUCCESS) { 442 | state->inputReplayFd = open(state->replayInputPath, O_WRONLY | O_CREAT, 0644); 443 | } else { 444 | // TODO(zach) Logging. 445 | } 446 | state->replayState = RECORDING; 447 | } 448 | 449 | internal void iosEndRecordingInput(ios_state *state) { 450 | close(state->inputReplayFd); 451 | state->inputReplayFd = 0; 452 | state->replayState = NONE; 453 | } 454 | 455 | internal void iosBeginPlaybackInput(ios_state *state) { 456 | kern_return_t result = vm_copy( 457 | (vm_map_t)mach_task_self(), 458 | (vm_address_t)state->replayBuffer.memoryBlock, 459 | (vm_size_t)state->storageMemorySize, 460 | (vm_address_t)state->storageMemory ); 461 | if (result == KERN_SUCCESS) { 462 | state->inputReplayFd = open(state->replayInputPath, O_RDONLY, 0644); 463 | } else { 464 | // TODO(zach) Logging. 465 | } 466 | state->replayState = PLAYBACK; 467 | } 468 | 469 | internal void iosEndPlaybackInput(ios_state *state) { 470 | close(state->inputReplayFd); 471 | state->inputReplayFd = 0; 472 | state->replayState = NONE; 473 | } 474 | 475 | internal void iosRecordInput(ios_state *state, ios_input *input) { 476 | if (write(state->inputReplayFd, input, sizeof(ios_input)) != sizeof(ios_input)) { 477 | // TODO(zach): Logging. 478 | } 479 | } 480 | 481 | internal void iosPlaybackInput(ios_state *state, ios_input *input) { 482 | switch (read(state->inputReplayFd, input, sizeof(ios_input))) { 483 | case -1: 484 | // TODO(zach): Logging. 485 | break; 486 | case 0: 487 | // NOTE(zach): Need to rewind 488 | iosEndPlaybackInput(state); 489 | iosBeginPlaybackInput(state); 490 | iosPlaybackInput(state, input); 491 | break; 492 | default: 493 | break; 494 | } 495 | } 496 | 497 | // 498 | // 499 | // 500 | 501 | @interface Handmade_viewcontroller : UIViewController 502 | @end 503 | 504 | @implementation Handmade_viewcontroller 505 | 506 | - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { 507 | return UIInterfaceOrientationLandscapeRight; 508 | } 509 | 510 | - (NSUInteger)supportedInterfaceOrientations { 511 | return UIInterfaceOrientationMaskLandscapeRight; 512 | } 513 | 514 | - (BOOL)shouldAutorotate { 515 | return YES; 516 | } 517 | 518 | @end 519 | 520 | // 521 | // 522 | // 523 | 524 | @implementation App_delegate 525 | 526 | - (void)touchEvent:(UIEvent *)event { 527 | NSArray *allTouches = [[event allTouches] allObjects]; 528 | 529 | // TODO(zach): Find right answer for this 530 | #define MAX_TOUCHES 10 531 | 532 | // TODO(zach): Need to figure out if the touches returned by this function 533 | // are just the touches that changed, or if they include all active touches. 534 | ios_input_touch touchInputs[MAX_TOUCHES]; 535 | size_t touchNum = 0; 536 | for (UITouch *touch in allTouches) { 537 | CGPoint loc = [touch locationInView:nil]; 538 | touchInputs[touchNum].x = loc.x * globalState.pointToPixelScale; 539 | touchInputs[touchNum].y = globalBackBuffers[0].height - loc.y * globalState.pointToPixelScale; 540 | touchInputs[touchNum].radius = (touch.majorRadius + touch.majorRadiusTolerance) * 541 | globalState.pointToPixelScale; 542 | touchInputs[touchNum].phase = touch.phase; 543 | globalLastTouch = touchInputs[touchNum]; 544 | //NSLog(@"(%f, %f)\n", touchInputs[touchNum].x, touchInputs[touchNum].y); 545 | ++touchNum; 546 | } 547 | 548 | for (size_t i = 0; i < ArrayCount(globalInput.buttons); ++i) 549 | iosProccessRoundButtonInput(&globalInput.buttons[i], touchInputs, touchNum); 550 | 551 | #if IOS_HANDMADE_DEBUG_INPUT 552 | for (size_t i = 0; i < ArrayCount(globalInput.debugButtons); ++i) 553 | iosProccessRoundButtonInput(&globalInput.debugButtons[i], touchInputs, touchNum); 554 | #endif 555 | 556 | Float32 extraStickToleranceRadius = 200.0; 557 | ios_input_joystick *joystick = &globalInput.joystick; 558 | bool joystickActive = false; 559 | for (size_t i = 0; i < touchNum; ++i) { 560 | ios_input_touch touch = touchInputs[i]; 561 | Float32 dX = (touch.x - joystick->centerX); 562 | Float32 dY = (touch.y - joystick->centerY); 563 | Float32 distance = sqrt(dX * dX + dY * dY); 564 | if (distance - joystick->radius - touch.radius - extraStickToleranceRadius < 0) { 565 | Float32 scaleFactor = distance <= joystick->radius ? 566 | joystick->radius : distance; 567 | switch (touch.phase) { 568 | case UITouchPhaseBegan: 569 | case UITouchPhaseMoved: 570 | case UITouchPhaseStationary: 571 | joystick->stickX = dX / scaleFactor; 572 | joystick->stickY = dY / scaleFactor; 573 | joystickActive = true; 574 | break; 575 | case UITouchPhaseEnded: 576 | case UITouchPhaseCancelled: 577 | break; 578 | default: 579 | break; 580 | } 581 | } 582 | } 583 | if (!joystickActive) 584 | joystick->stickX = joystick->stickY = 0.0; 585 | } 586 | 587 | - (void)doFrame:(CADisplayLink *)sender { 588 | local_persist size_t bufferNo = 0; 589 | local_persist uint64_t tastTime = 0; 590 | local_persist real32 machToNano = 0.0; 591 | 592 | ios_offscreen_buffer activeBuffer = globalBackBuffers[bufferNo ^= 1]; 593 | 594 | // TODO(zach): Should we use CGColorSpaceCreateCalibratedRGB() ? 595 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 596 | 597 | // IMPORTANT(zach): 32bpp, 8bpc, ARGB, premultiplied alpha, little endian. 598 | CGContextRef context = CGBitmapContextCreate( 599 | activeBuffer.memory, 600 | activeBuffer.width, 601 | activeBuffer.height, 602 | 8, 603 | activeBuffer.pitch, 604 | colorSpace, 605 | (CGBitmapInfo)(kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little) ); 606 | 607 | CGColorSpaceRelease(colorSpace); 608 | 609 | if (context) { 610 | CGContextSetRGBFillColor(context, 0, 0, 0, 1); 611 | CGContextFillRect(context, CGRectMake(0, 0, activeBuffer.width, activeBuffer.height)); 612 | 613 | thread_context thread = {0}; 614 | 615 | game_offscreen_buffer gameBuffer; 616 | gameBuffer.Memory = activeBuffer.memory; 617 | #define HANDMADE_PIXEL_WIDTH 960 618 | #define HANDMADE_PIXEL_HEIGHT 540 619 | // NOTE(zach): Casey's software renderer does not scale right now. It expects 620 | // a 960x540 pixel buffer to render into 621 | gameBuffer.Width = MIN(HANDMADE_PIXEL_WIDTH, activeBuffer.width); 622 | gameBuffer.Height = MIN(HANDMADE_PIXEL_HEIGHT, activeBuffer.height);; 623 | gameBuffer.Pitch = activeBuffer.pitch; 624 | gameBuffer.BytesPerPixel = activeBuffer.bytesPerPixel; 625 | 626 | #pragma clang diagnostic push 627 | #pragma clang diagnostic ignored "-Wmissing-braces" 628 | game_input gameInput = {0}; 629 | #pragma clang diagnostic pop 630 | if (globalInput.debugLoop.halfTransitionCount > 0 && globalInput.debugLoop.isDown) { 631 | switch (globalState.replayState) { 632 | case NONE: 633 | iosBeginRecordingInput(&globalState); 634 | globalState.replayState = RECORDING; 635 | break; 636 | case RECORDING: 637 | iosEndRecordingInput(&globalState); 638 | iosBeginPlaybackInput(&globalState); 639 | globalState.replayState = PLAYBACK; 640 | break; 641 | case PLAYBACK: 642 | iosEndPlaybackInput(&globalState); 643 | globalState.replayState = NONE; 644 | break; 645 | } 646 | } 647 | #pragma clang diagnostic push 648 | #pragma clang diagnostic ignored "-Wswitch" 649 | switch (globalState.replayState) { 650 | case RECORDING: 651 | iosRecordInput(&globalState, &globalInput); 652 | break; 653 | case PLAYBACK: 654 | iosPlaybackInput(&globalState, &globalInput); 655 | break; 656 | } 657 | #pragma clang diagnostic pop 658 | 659 | mapInputToGame(&globalInput, &gameInput); 660 | 661 | uint64_t now = mach_absolute_time(); 662 | if (machToNano == 0.0 ) { 663 | mach_timebase_info_data_t sTimebaseInfo; 664 | mach_timebase_info(&sTimebaseInfo); 665 | machToNano = (real32)sTimebaseInfo.numer / (real32)sTimebaseInfo.denom; 666 | } 667 | #define NANOSECONDS_PER_S 1000000000 668 | gameInput.dtForFrame = (real32)(now - tastTime) * machToNano / (real32)NANOSECONDS_PER_S; 669 | tastTime = now; 670 | 671 | GameUpdateAndRender(&thread, &globalGameMemory, &gameInput, &gameBuffer); 672 | iosRenderInputHud(context, &activeBuffer, &globalInput); 673 | #if IOS_HANDMADE_DEBUG 674 | iosRenderDebugInputHud(context, &activeBuffer, &globalInput); 675 | #endif 676 | renderTouch(context, globalLastTouch); 677 | 678 | // NOTE(zach): For now, blit the bitmap returned by game code directly to screen. 679 | // I.e. don't scale the image to fit screen. Not that it would be hard to do so, 680 | // but because 1->1 byte to pixel mapping is usefull during development, and I'm 681 | // not sure if Casey will be doing scaling in his software render. 682 | CGImageRef image = CGBitmapContextCreateImage(context); 683 | CGContextRelease(context); 684 | 685 | // NOTE(zach): Initial testing shows this is faster than drawRect: or drawLayer:InContext: 686 | self.window.layer.contents = (id)image; 687 | 688 | // layer.contents is strong reference so can release immidiatly 689 | CGImageRelease(image); 690 | } else { 691 | NSLog(@"Couldn't create graphics context\n"); 692 | // TODO(zach): logging 693 | } 694 | 695 | for (size_t i = 0; i < ArrayCount(globalInput.buttons); ++i) 696 | globalInput.buttons[i].halfTransitionCount = 0; 697 | #if IOS_HANDMADE_DEBUG_INPUT 698 | for (size_t i = 0; i < ArrayCount(globalInput.debugButtons); ++i) 699 | globalInput.debugButtons[i].halfTransitionCount = 0; 700 | #endif 701 | } 702 | 703 | - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 704 | 705 | NSString *sourcePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"code/data"]; 706 | NSString *destPath = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) firstObject]; 707 | // TODO(zach): Probably don't need to do this everytime. 708 | NSError *loadErr = iosLoadWritableBundleData(sourcePath, destPath); 709 | if (loadErr) { 710 | // TODO(zach): Logging. 711 | } 712 | if (![destPath getCString:globalState.writableDataPath 713 | maxLength:ArrayCount(globalState.writableDataPath) 714 | encoding:NSASCIIStringEncoding]) { 715 | // TODO(zach): Logging. 716 | } 717 | 718 | // NOTE(zach): Set frame after window gets it's orientation. When landscape app is ran 719 | // from portrait, need to do this so window has the right frame. 720 | // 721 | // http://stackoverflow.com/questions/25963101/unexpected-nil-window-in-uiapplicationhandleeventfromqueueevent 722 | self.window = [[UIWindow alloc] init]; 723 | self.window.rootViewController = [[Handmade_viewcontroller alloc] init]; 724 | [self.window makeKeyAndVisible]; 725 | self.window.frame = [[UIScreen mainScreen] bounds]; 726 | 727 | // IMPORTANT(zach): Make sure there exists a launch screen for the suitable device. If you don't 728 | // have a launch screen for a particular screen size, Apple interprets this as your app not 729 | // supporting that screen size, and will launch in letterbox mode. 730 | 731 | globalState.pointToPixelScale = [UIScreen mainScreen].scale; 732 | CGSize screen = [[UIScreen mainScreen] nativeBounds].size; 733 | int pixelWidth = (int)(screen.width + 0.5); 734 | int pixelHeight = (int)(screen.height + 0.5); 735 | if (pixelWidth < pixelHeight) { 736 | int tmp = pixelWidth; 737 | pixelWidth = pixelHeight; 738 | pixelHeight = tmp; 739 | } 740 | int bytesPerPixel = 4; 741 | 742 | globalBackBuffers[0].height = pixelHeight; 743 | globalBackBuffers[0].width = pixelWidth; 744 | globalBackBuffers[0].bytesPerPixel = bytesPerPixel; 745 | globalBackBuffers[0].pitch = pixelWidth * bytesPerPixel; 746 | globalBackBuffers[1] = globalBackBuffers[0]; 747 | 748 | globalGameMemory.PermanentStorageSize = Megabytes(256); 749 | globalGameMemory.TransientStorageSize = Megabytes(64); 750 | globalGameMemory.IsInitialized = (bool32)false; 751 | 752 | #if HANDMADE_INTERNAL 753 | globalGameMemory.DEBUGPlatformFreeFileMemory = debugPlatformFreeFileMemory; 754 | globalGameMemory.DEBUGPlatformReadEntireFile = debugPlatformReadEntireFile; 755 | globalGameMemory.DEBUGPlatformWriteEntireFile = debugPlatformWriteEntireFile; 756 | #endif 757 | 758 | size_t bitmapSize = pixelWidth * pixelHeight * bytesPerPixel; 759 | size_t gameStorageSize = (size_t)globalGameMemory.PermanentStorageSize + 760 | (size_t)globalGameMemory.TransientStorageSize; 761 | 762 | globalState.totalMemorySize = gameStorageSize + bitmapSize * 2; 763 | globalState.storageMemorySize = gameStorageSize; 764 | 765 | iosBuildFilePathUnchecked(globalState.writableDataPath, "replay_loop_state.hmi", 766 | globalState.replayBuffer.fileName); 767 | 768 | iosBuildFilePathUnchecked(globalState.writableDataPath, "replay_loop_input.hmi", 769 | globalState.replayInputPath); 770 | 771 | if ((globalState.replayBuffer.fd = open(globalState.replayBuffer.fileName, O_RDWR | O_CREAT, 0644)) != 1) { 772 | struct stat sbuff; 773 | if (stat(globalState.replayBuffer.fileName, &sbuff) != -1) { 774 | if (sbuff.st_size != gameStorageSize) { 775 | fstore_t fstore = {0}; 776 | fstore.fst_flags = F_ALLOCATECONTIG | F_ALLOCATEALL, 777 | fstore.fst_posmode = F_PEOFPOSMODE; 778 | fstore.fst_offset = 0; 779 | fstore.fst_length = gameStorageSize; 780 | fstore.fst_bytesalloc = 0; 781 | 782 | int result = fcntl(globalState.replayBuffer.fd, F_PREALLOCATE, &fstore); 783 | if (result != -1) { 784 | if ((result = ftruncate(globalState.replayBuffer.fd, gameStorageSize)) != -1) { 785 | if (stat(globalState.replayBuffer.fileName, &sbuff) != -1) { 786 | Assert(sbuff.st_size == gameStorageSize); 787 | } 788 | } else { 789 | // TODO(zach): Logging. 790 | } 791 | } else { 792 | // TODO(zach): Logging. 793 | } 794 | } 795 | } else { 796 | // TODO(zach): Logging. 797 | } 798 | } else { 799 | // TODO(zach): Logging. 800 | } 801 | 802 | // TODO(zach): How to do this at mach level with vm_map()? 803 | if ((globalState.replayBuffer.memoryBlock = mmap( 804 | NULL, 805 | gameStorageSize, 806 | PROT_READ | PROT_WRITE, 807 | MAP_FILE | MAP_SHARED, 808 | globalState.replayBuffer.fd, 809 | 0 )) == MAP_FAILED) { 810 | // TODO(zach): Logging. 811 | } 812 | 813 | // TODO(zach): Prefer this over mmap() ? 814 | vm_address_t baseAddress; 815 | kern_return_t result = vm_allocate( 816 | (vm_map_t)mach_task_self(), 817 | &baseAddress, 818 | (vm_size_t)(globalState.totalMemorySize), 819 | (boolean_t)true ); 820 | if (result != KERN_SUCCESS) { 821 | // TODO(zach): logging 822 | } 823 | 824 | globalState.memory = globalState.storageMemory = (void *)baseAddress; 825 | 826 | // NOTE(zach): vm_allocate() initializes pages to 0 as required by game code 827 | globalGameMemory.PermanentStorage = (void *)baseAddress; 828 | globalGameMemory.TransientStorage = (void *)((uint8_t *)baseAddress + 829 | globalGameMemory.PermanentStorageSize); 830 | globalBackBuffers[0].memory = (void *)((uint8_t *)baseAddress + 831 | globalGameMemory.PermanentStorageSize + 832 | globalGameMemory.TransientStorageSize); 833 | globalBackBuffers[1].memory = (void *)((uint8_t *)baseAddress + 834 | globalGameMemory.PermanentStorageSize + 835 | globalGameMemory.TransientStorageSize + 836 | bitmapSize); 837 | 838 | globalSoundOutput.samplesHz = 44100; 839 | globalSoundOutput.bytesPerChannelPerPacket = sizeof(uint16_t); 840 | globalSoundOutput.bytesPerPacket = 2 * sizeof(uint16_t); 841 | 842 | OSErr soundErr; 843 | if ((soundErr = iosInitAudioUnit(&globalSoundOutput)) != noErr) { 844 | // TODO(zach): Logging 845 | } else if ((soundErr = AudioUnitInitialize(globalSoundOutput.audioUnit)) != noErr) { 846 | // TODO(zach): Logging 847 | } else if ((soundErr = AudioOutputUnitStart(globalSoundOutput.audioUnit)) != noErr ) { 848 | // IDEA(zach): Maybe manually ask AudioUnit to render? That way we can sync with 849 | // drawing and provide our own memory 850 | // 851 | // TODO(zach): Logging 852 | } 853 | 854 | iosInitInput(&globalInput, &globalBackBuffers[0]); 855 | 856 | // NOTE(zach): Testing looks like CADisplayLink waits for vsync. 857 | // Regardless of frameInterval, if you miss a frame, CADisplayLink will be 858 | // called on next possible vsync - even if it is not a multiple of frameInterval. 859 | // 860 | // TODO(zach): Need to look more into what CADisplayLink does when we miss a frame, since 861 | // potentially can be wasting a lot of time waiting for vsync if we overshoot 862 | // our frame by just a little. 863 | // 864 | // http://www.gamasutra.com/blogs/KwasiMensah/20110211/88949/Game_Loops_on_IOS.php 865 | globalDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(doFrame:)]; 866 | globalDisplayLink.frameInterval = 2; 867 | [globalDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; 868 | 869 | return YES; 870 | } 871 | 872 | - (void)applicationWillResignActive:(UIApplication *)application { 873 | // TODO(zach): Implement later 874 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 875 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 876 | } 877 | 878 | - (void)applicationDidEnterBackground:(UIApplication *)application { 879 | // TODO(zach): Implement later 880 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 881 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 882 | } 883 | 884 | - (void)applicationWillEnterForeground:(UIApplication *)application { 885 | // TODO(zach): Implement later 886 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 887 | } 888 | 889 | - (void)applicationDidBecomeActive:(UIApplication *)application { 890 | // TODO(zach): Implement later 891 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 892 | } 893 | 894 | - (void)applicationWillTerminate:(UIApplication *)application { 895 | // TODO(zach): Implement later 896 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 897 | } 898 | 899 | @end 900 | 901 | -------------------------------------------------------------------------------- /ios_handmade/ios_main.m: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | #import "ios_app_delegate.h" 4 | 5 | @interface Handmade_application : UIApplication 6 | @end 7 | 8 | @implementation Handmade_application 9 | 10 | - (void)sendEvent:(UIEvent *)event { 11 | if ([event type] == UIEventTypeTouches) 12 | [(App_delegate *)[[UIApplication sharedApplication] delegate] touchEvent:event]; 13 | else 14 | [super sendEvent: event]; 15 | } 16 | 17 | @end 18 | 19 | int main(int argc, char * argv[]) { 20 | @autoreleasepool { 21 | return UIApplicationMain(argc, argv, NSStringFromClass([Handmade_application class]), 22 | NSStringFromClass([App_delegate class])); 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /screenshots/day40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zokeefe/ios_handmade/fd93f4cebb5fe6d2cbc0b2802db95f934dce357a/screenshots/day40.png -------------------------------------------------------------------------------- /screenshots/day40_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zokeefe/ios_handmade/fd93f4cebb5fe6d2cbc0b2802db95f934dce357a/screenshots/day40_2.png -------------------------------------------------------------------------------- /screenshots/day40_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zokeefe/ios_handmade/fd93f4cebb5fe6d2cbc0b2802db95f934dce357a/screenshots/day40_3.png -------------------------------------------------------------------------------- /screenshots/day40_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zokeefe/ios_handmade/fd93f4cebb5fe6d2cbc0b2802db95f934dce357a/screenshots/day40_4.png -------------------------------------------------------------------------------- /screenshots/day52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zokeefe/ios_handmade/fd93f4cebb5fe6d2cbc0b2802db95f934dce357a/screenshots/day52.png -------------------------------------------------------------------------------- /screenshots/day69.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zokeefe/ios_handmade/fd93f4cebb5fe6d2cbc0b2802db95f934dce357a/screenshots/day69.png --------------------------------------------------------------------------------