├── .gitignore ├── Podfile ├── Podfile.lock ├── README.md ├── SwiftRPG.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── SwiftRPG.xcworkspace └── contents.xcworkspacedata ├── SwiftRPG ├── Supporting Files │ ├── GameScene.sks │ └── Info.plist ├── resources │ ├── image │ │ ├── Character │ │ │ ├── mob.xcassets │ │ │ │ ├── Contents.json │ │ │ │ └── mob.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── mob.png │ │ │ └── player.xcassets │ │ │ │ ├── Contents.json │ │ │ │ ├── player.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── player.png │ │ │ │ ├── plr_down.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── plr_down.png │ │ │ │ ├── plr_down_01.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── plr_down_01.png │ │ │ │ ├── plr_down_02.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── plr_down_02.png │ │ │ │ ├── plr_left.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── plr_left.png │ │ │ │ ├── plr_left_01.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── plr_left_01.png │ │ │ │ ├── plr_left_02.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── plr_left_02.png │ │ │ │ ├── plr_right.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── plr_right.png │ │ │ │ ├── plr_right_01.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── plr_right_01.png │ │ │ │ ├── plr_right_02.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── plr_right_02.png │ │ │ │ ├── plr_up.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── plr_up.png │ │ │ │ ├── plr_up_01.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── plr_up_01.png │ │ │ │ └── plr_up_02.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── plr_up_02.png │ │ ├── Images.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── start.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── start.png │ │ └── Map │ │ │ ├── black.png │ │ │ ├── kanamonoMap.xcassets │ │ │ ├── Contents.json │ │ │ ├── collisions.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── collisions.png │ │ │ └── kanamono_tile.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── kanamono_tile.png │ │ │ ├── sample_map01.json │ │ │ └── sample_map02.json │ ├── talk.wav │ └── talking │ │ └── talk_01.txt └── src │ ├── AppDelegate.swift │ ├── Controller │ ├── GameViewController.swift │ ├── MenuViewController.swift │ └── TitleViewController.swift │ ├── Model │ ├── A-star │ │ ├── AStar.swift │ │ └── Node.swift │ ├── DataModel │ │ └── DataModels.swift │ ├── Dialog │ │ ├── Dialog.swift │ │ └── TalkBodyParser.swift │ ├── Event │ │ ├── EventDispatcher.swift │ │ ├── EventListener.swift │ │ ├── EventListener │ │ │ ├── ActivateButtonListener.swift │ │ │ ├── BackToDefaultStateEventListener.swift │ │ │ ├── Event Dialog │ │ │ │ ├── HideEventDialogListener.swift │ │ │ │ └── ShowEventDialogListener.swift │ │ │ ├── EventListenerImplement.swift │ │ │ ├── InvokeNextEventListener.swift │ │ │ ├── ItemGetEventListener.swift │ │ │ ├── MoveObjectEventListener.swift │ │ │ ├── ReloadBehaviorEventListener.swift │ │ │ ├── RenderDefaultViewEventListener.swift │ │ │ ├── SceneTransitionEventListener.swift │ │ │ ├── Talk Event │ │ │ │ ├── FinishTalkEventListener.swift │ │ │ │ ├── StartTalkEventListener.swift │ │ │ │ └── TalkEventListener.swift │ │ │ ├── WaitEventListener.swift │ │ │ ├── WalkEventListener.swift │ │ │ └── WalkOneStepEventListener.swift │ │ └── EventManager.swift │ ├── Map │ │ ├── Map.swift │ │ ├── TileSheet │ │ │ ├── MapObject │ │ │ │ ├── EventObject.swift │ │ │ │ ├── MapObject.swift │ │ │ │ ├── Object.swift │ │ │ │ ├── Tile.swift │ │ │ │ └── util │ │ │ │ │ ├── BehaviorPropertyParser.swift │ │ │ │ │ ├── EventPropertyParser.swift │ │ │ │ │ ├── ListenerContainer.swift │ │ │ │ │ └── ListenerGenerator.swift │ │ │ ├── TileCoordinate.swift │ │ │ ├── TileSet.swift │ │ │ └── TileSheet.swift │ │ └── TiledMapJsonParser.swift │ ├── MenuSceneModel.swift │ ├── Setting │ │ ├── ItemTable.swift │ │ ├── MapTable.swift │ │ ├── objectNameTable.swift │ │ ├── talkerImage.swift │ │ └── zPositionTable.swift │ └── common │ │ ├── DIRECTION.swift │ │ ├── DialogLabel.swift │ │ ├── IMAGE_SET.swift │ │ ├── String+subscript.swift │ │ ├── UIButton+title.swift │ │ ├── UIButtonAnimated.swift │ │ └── myButton.swift │ └── View │ ├── FirstGameScene.swift │ ├── GameScene.swift │ ├── MenuScene.swift │ ├── MenuScene.xib │ ├── TitleScene.swift │ ├── TitleScene.xib │ ├── animation │ └── TransitionToMenuAnimator.swift │ └── components │ ├── ItemCell.swift │ ├── ItemCell.xib │ └── SecondGameScene.swift ├── SwiftRPGTests ├── Supporting Files │ └── Info.plist └── SwiftRPGTests.swift └── readme_resources └── movie.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # Xcode 3 | # 4 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 5 | 6 | ## Build generated 7 | build/ 8 | DerivedData 9 | 10 | ## Various settings 11 | *.pbxuser 12 | !default.pbxuser 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | *.perspectivev3 18 | !default.perspectivev3 19 | xcuserdata 20 | 21 | ## Other 22 | *.xccheckout 23 | *.moved-aside 24 | *.xcuserstate 25 | *.xcscmblueprint 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 36 | # 37 | Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | .ruby-version 47 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | platform :ios, '8.0' 3 | use_frameworks! 4 | 5 | pod 'SwiftyJSON', :git => 'https://github.com/SwiftyJSON/SwiftyJSON.git' 6 | pod 'JSONSchema', :git => 'https://github.com/kylef/JSONSchema.swift.git', :branch => 'master' 7 | pod 'RealmSwift' 8 | pod "PromiseKit", "~> 4.0" 9 | 10 | target 'SwiftRPG' do 11 | 12 | end 13 | 14 | target 'SwiftRPGTests' do 15 | 16 | end 17 | 18 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - JSONSchema (0.4.0) 3 | - PromiseKit (4.0.4): 4 | - PromiseKit/Foundation (= 4.0.4) 5 | - PromiseKit/QuartzCore (= 4.0.4) 6 | - PromiseKit/UIKit (= 4.0.4) 7 | - PromiseKit/CorePromise (4.0.4) 8 | - PromiseKit/Foundation (4.0.4): 9 | - PromiseKit/CorePromise 10 | - PromiseKit/QuartzCore (4.0.4): 11 | - PromiseKit/CorePromise 12 | - PromiseKit/UIKit (4.0.4): 13 | - PromiseKit/CorePromise 14 | - Realm (2.0.3): 15 | - Realm/Headers (= 2.0.3) 16 | - Realm/Headers (2.0.3) 17 | - RealmSwift (2.0.3): 18 | - Realm (= 2.0.3) 19 | - SwiftyJSON (3.1.1) 20 | 21 | DEPENDENCIES: 22 | - JSONSchema (from `https://github.com/kylef/JSONSchema.swift.git`, branch `master`) 23 | - PromiseKit (~> 4.0) 24 | - RealmSwift 25 | - SwiftyJSON (from `https://github.com/SwiftyJSON/SwiftyJSON.git`) 26 | 27 | EXTERNAL SOURCES: 28 | JSONSchema: 29 | :branch: master 30 | :git: https://github.com/kylef/JSONSchema.swift.git 31 | SwiftyJSON: 32 | :git: https://github.com/SwiftyJSON/SwiftyJSON.git 33 | 34 | CHECKOUT OPTIONS: 35 | JSONSchema: 36 | :commit: 8f8440e6766fe0d34bf50833c1d5e70a71c999d1 37 | :git: https://github.com/kylef/JSONSchema.swift.git 38 | SwiftyJSON: 39 | :commit: 1dc2ce0f913b27380cd5b803af8aa853745b9608 40 | :git: https://github.com/SwiftyJSON/SwiftyJSON.git 41 | 42 | SPEC CHECKSUMS: 43 | JSONSchema: 6983b7ba0b0711e9a92bcc789a4bfc16fc351ec7 44 | PromiseKit: 8e8ee39d33ff199d92a1a883b5396f6285be6c91 45 | Realm: 5f008bfe3c8c47142eddfc30b8c1584cde22db24 46 | RealmSwift: 5afb451f65b682242f272dbb52d5c6f6e343c980 47 | SwiftyJSON: f0be2e604f83e8405a624e9f891898bf6ed4e019 48 | 49 | PODFILE CHECKSUM: 7966587d84cc0c98820010aea99d83db49a241d0 50 | 51 | COCOAPODS: 1.1.1 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift RPG 2 | 3 | Simple game written in swift with SpriteKit. 4 | *Work in progress* 5 | 6 | ## Document 7 | 8 | [here](https://github.com/tasuwo/SwiftRPG/wiki) 9 | 10 | ## Screen Shot 11 | 12 | ![screen_shot](./readme_resources/movie.gif) 13 | 14 | ## TODO 15 | 16 | - ~~Move objects~~ 17 | - ~~Collision detection management~~ 18 | - ~~Stop object's behavior when touch event occured~~ 19 | - ~~Loop object's behavior~~ 20 | - ~~Disabling walking and behavior~~ 21 | - ~~Scene transition~~ 22 | - ~~Use only SpriteKit. Use UIKit for using storyboard, but it's not efficiency.~~ 23 | - Sound 24 | - Flag management 25 | - Battle 26 | - Document 27 | 28 | ## BUG 29 | 30 | - ~~Talk with npc during npc moving, the game would be crashed.~~ 31 | -------------------------------------------------------------------------------- /SwiftRPG.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftRPG.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SwiftRPG/Supporting Files/GameScene.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/Supporting Files/GameScene.sks -------------------------------------------------------------------------------- /SwiftRPG/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 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 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarHidden 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationLandscapeLeft 36 | UIInterfaceOrientationLandscapeRight 37 | 38 | UISupportedInterfaceOrientations~ipad 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationPortraitUpsideDown 42 | UIInterfaceOrientationLandscapeLeft 43 | UIInterfaceOrientationLandscapeRight 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/mob.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/mob.xcassets/mob.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "mob.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/mob.xcassets/mob.imageset/mob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Character/mob.xcassets/mob.imageset/mob.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/player.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "player.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/player.imageset/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Character/player.xcassets/player.imageset/player.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_down.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "plr_down.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_down.imageset/plr_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Character/player.xcassets/plr_down.imageset/plr_down.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_down_01.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "plr_down_01.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_down_01.imageset/plr_down_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Character/player.xcassets/plr_down_01.imageset/plr_down_01.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_down_02.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "plr_down_02.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_down_02.imageset/plr_down_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Character/player.xcassets/plr_down_02.imageset/plr_down_02.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_left.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "plr_left.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_left.imageset/plr_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Character/player.xcassets/plr_left.imageset/plr_left.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_left_01.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "plr_left_01.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_left_01.imageset/plr_left_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Character/player.xcassets/plr_left_01.imageset/plr_left_01.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_left_02.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "plr_left_02.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_left_02.imageset/plr_left_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Character/player.xcassets/plr_left_02.imageset/plr_left_02.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_right.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "plr_right.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_right.imageset/plr_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Character/player.xcassets/plr_right.imageset/plr_right.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_right_01.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "plr_right_01.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_right_01.imageset/plr_right_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Character/player.xcassets/plr_right_01.imageset/plr_right_01.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_right_02.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "plr_right_02.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_right_02.imageset/plr_right_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Character/player.xcassets/plr_right_02.imageset/plr_right_02.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_up.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "plr_up.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_up.imageset/plr_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Character/player.xcassets/plr_up.imageset/plr_up.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_up_01.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "plr_up_01.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_up_01.imageset/plr_up_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Character/player.xcassets/plr_up_01.imageset/plr_up_01.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_up_02.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "plr_up_02.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Character/player.xcassets/plr_up_02.imageset/plr_up_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Character/player.xcassets/plr_up_02.imageset/plr_up_02.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Images.xcassets/start.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "start.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Images.xcassets/start.imageset/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Images.xcassets/start.imageset/start.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Map/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Map/black.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Map/kanamonoMap.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Map/kanamonoMap.xcassets/collisions.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "collisions.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Map/kanamonoMap.xcassets/collisions.imageset/collisions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Map/kanamonoMap.xcassets/collisions.imageset/collisions.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Map/kanamonoMap.xcassets/kanamono_tile.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "kanamono_tile.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Map/kanamonoMap.xcassets/kanamono_tile.imageset/kanamono_tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/image/Map/kanamonoMap.xcassets/kanamono_tile.imageset/kanamono_tile.png -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Map/sample_map01.json: -------------------------------------------------------------------------------- 1 | { "height":20, 2 | "layers":[ 3 | { 4 | "data":[6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 13, 13, 13, 13, 13, 13, 13, 13, 13, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 13, 13, 13, 13, 13, 13, 13, 13, 13, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 13, 13, 13, 6, 6, 6, 13, 13, 13, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 13, 13, 13, 6, 6, 6, 13, 13, 13, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 13, 13, 13, 6, 6, 6, 13, 13, 13, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 13, 13, 13, 13, 13, 13, 13, 13, 13, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 13, 13, 13, 13, 13, 13, 13, 13, 13, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], 5 | "height":20, 6 | "name":"tile", 7 | "opacity":1, 8 | "type":"tilelayer", 9 | "visible":true, 10 | "width":20, 11 | "x":0, 12 | "y":0 13 | }, 14 | { 15 | "data":[12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 12, 12, 12, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 12, 12, 12, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 12, 12, 12, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], 16 | "height":20, 17 | "name":"collision", 18 | "opacity":0.310000002384186, 19 | "type":"tilelayer", 20 | "visible":true, 21 | "width":20, 22 | "x":0, 23 | "y":0 24 | }, 25 | { 26 | "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 27 | "height":20, 28 | "name":"object", 29 | "opacity":1, 30 | "type":"tilelayer", 31 | "visible":true, 32 | "width":20, 33 | "x":0, 34 | "y":0 35 | }], 36 | "nextobjectid":1, 37 | "orientation":"orthogonal", 38 | "renderorder":"right-up", 39 | "tileheight":32, 40 | "tilesets":[ 41 | { 42 | "columns":6, 43 | "firstgid":1, 44 | "image":"kanamonoMap.xcassets\/collisions.imageset\/collisions.png", 45 | "imageheight":64, 46 | "imagewidth":192, 47 | "margin":0, 48 | "name":"collisions", 49 | "spacing":0, 50 | "tilecount":12, 51 | "tileheight":32, 52 | "tileproperties": 53 | { 54 | "0": 55 | { 56 | "collision":"1" 57 | }, 58 | "1": 59 | { 60 | "collision":"1" 61 | }, 62 | "11": 63 | { 64 | "event":"scene,{(0,0)},sample_map02.json,10-10,UP" 65 | }, 66 | "6": 67 | { 68 | "collision":"1" 69 | } 70 | }, 71 | "tilepropertytypes": 72 | { 73 | "0": 74 | { 75 | "collision":"string" 76 | }, 77 | "1": 78 | { 79 | "collision":"string" 80 | }, 81 | "11": 82 | { 83 | "event":"string" 84 | }, 85 | "6": 86 | { 87 | "collision":"string" 88 | } 89 | }, 90 | "tilewidth":32 91 | }, 92 | { 93 | "columns":1, 94 | "firstgid":13, 95 | "image":"kanamonoMap.xcassets\/kanamono_tile.imageset\/kanamono_tile.png", 96 | "imageheight":32, 97 | "imagewidth":32, 98 | "margin":0, 99 | "name":"kanamono_tile", 100 | "spacing":0, 101 | "tilecount":1, 102 | "tileheight":32, 103 | "tileproperties": 104 | { 105 | "0": 106 | { 107 | "collision":"0" 108 | } 109 | }, 110 | "tilepropertytypes": 111 | { 112 | "0": 113 | { 114 | "collision":"string" 115 | } 116 | }, 117 | "tilewidth":32 118 | }, 119 | { 120 | "columns":1, 121 | "firstgid":14, 122 | "image":"..\/Character\/mob.xcassets\/mob.imageset\/mob.png", 123 | "imageheight":64, 124 | "imagewidth":32, 125 | "margin":0, 126 | "name":"mob", 127 | "spacing":0, 128 | "tilecount":1, 129 | "tileheight":64, 130 | "tileproperties": 131 | { 132 | "0": 133 | { 134 | "behavior":"wait,1\nmove,mob,UP,1,1\nwait,1\nmove,mob,DOWN,1,1", 135 | "collision":"1", 136 | "event":"talk,{(0,-1)},talk_01.txt" 137 | } 138 | }, 139 | "tilepropertytypes": 140 | { 141 | "0": 142 | { 143 | "behavior":"string", 144 | "collision":"string", 145 | "event":"string" 146 | } 147 | }, 148 | "tilewidth":32 149 | }], 150 | "tilewidth":32, 151 | "version":1, 152 | "width":20 153 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/image/Map/sample_map02.json: -------------------------------------------------------------------------------- 1 | { "height":20, 2 | "layers":[ 3 | { 4 | "data":[6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9, 8, 8, 8, 8, 8, 8, 8, 8, 11, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 10, 6, 6, 6, 6, 6, 6, 6, 7, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 10, 6, 6, 6, 6, 6, 6, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 10, 6, 6, 6, 6, 6, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 10, 6, 6, 6, 6, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 10, 6, 6, 6, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 6, 6, 6, 2, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 6, 6, 6, 6, 2, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 6, 6, 6, 6, 6, 2, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 5, 6, 6, 6, 6, 6, 6, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], 5 | "height":20, 6 | "name":"tile", 7 | "opacity":1, 8 | "type":"tilelayer", 9 | "visible":false, 10 | "width":20, 11 | "x":0, 12 | "y":0 13 | }, 14 | { 15 | "data":[12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], 16 | "height":20, 17 | "name":"collision", 18 | "opacity":0.430000007152557, 19 | "type":"tilelayer", 20 | "visible":false, 21 | "width":20, 22 | "x":0, 23 | "y":0 24 | }, 25 | { 26 | "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 27 | "height":20, 28 | "name":"object", 29 | "opacity":1, 30 | "type":"tilelayer", 31 | "visible":true, 32 | "width":20, 33 | "x":0, 34 | "y":0 35 | }], 36 | "nextobjectid":1, 37 | "orientation":"orthogonal", 38 | "renderorder":"right-up", 39 | "tileheight":32, 40 | "tilesets":[ 41 | { 42 | "columns":6, 43 | "firstgid":1, 44 | "image":"kanamonoMap.xcassets\/collisions.imageset\/collisions.png", 45 | "imageheight":64, 46 | "imagewidth":192, 47 | "margin":0, 48 | "name":"collisions", 49 | "spacing":0, 50 | "tilecount":12, 51 | "tileheight":32, 52 | "tileproperties": 53 | { 54 | "0": 55 | { 56 | "collision":"1" 57 | }, 58 | "1": 59 | { 60 | "collision":"1" 61 | }, 62 | "11": 63 | { 64 | "event":"scene,{(0,0)},sample_map01.json,8-7,UP" 65 | }, 66 | "6": 67 | { 68 | "collision":"1" 69 | } 70 | }, 71 | "tilepropertytypes": 72 | { 73 | "0": 74 | { 75 | "collision":"string" 76 | }, 77 | "1": 78 | { 79 | "collision":"string" 80 | }, 81 | "11": 82 | { 83 | "event":"string" 84 | }, 85 | "6": 86 | { 87 | "collision":"string" 88 | } 89 | }, 90 | "tilewidth":32 91 | }, 92 | { 93 | "columns":1, 94 | "firstgid":13, 95 | "image":"kanamonoMap.xcassets\/kanamono_tile.imageset\/kanamono_tile.png", 96 | "imageheight":32, 97 | "imagewidth":32, 98 | "margin":0, 99 | "name":"kanamono_tile", 100 | "spacing":0, 101 | "tilecount":1, 102 | "tileheight":32, 103 | "tileproperties": 104 | { 105 | "0": 106 | { 107 | "collision":"0" 108 | } 109 | }, 110 | "tilepropertytypes": 111 | { 112 | "0": 113 | { 114 | "collision":"string" 115 | } 116 | }, 117 | "tilewidth":32 118 | }, 119 | { 120 | "columns":1, 121 | "firstgid":14, 122 | "image":"..\/Character\/mob.xcassets\/mob.imageset\/mob.png", 123 | "imageheight":64, 124 | "imagewidth":32, 125 | "margin":0, 126 | "name":"mob", 127 | "spacing":0, 128 | "tilecount":1, 129 | "tileheight":64, 130 | "tileproperties": 131 | { 132 | "0": 133 | { 134 | "behavior":"wait,1\nmove,mob,LEFT,1,1\nwait,1\nmove,mob,RIGHT,1,1", 135 | "collision":"1", 136 | "event":"talk,{(0,-1)},talk_01.txt\nitem,{(0,-1)},test" 137 | } 138 | }, 139 | "tilepropertytypes": 140 | { 141 | "0": 142 | { 143 | "behavior":"string", 144 | "collision":"string", 145 | "event":"string" 146 | } 147 | }, 148 | "tilewidth":32 149 | }], 150 | "tilewidth":32, 151 | "version":1, 152 | "width":20 153 | } -------------------------------------------------------------------------------- /SwiftRPG/resources/talk.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/SwiftRPG/resources/talk.wav -------------------------------------------------------------------------------- /SwiftRPG/resources/talking/talk_01.txt: -------------------------------------------------------------------------------- 1 | player:L 2 | あいうえお. 3 | かきくけこ. 4 | ! 5 | player:R 6 | さしすせそ. 7 | たちつてと. 8 | ! -------------------------------------------------------------------------------- /SwiftRPG/src/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2015/06/27. 6 | // Copyright (c) 2015年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | /** 17 | * アプリケーション起動時に呼ばれる 18 | */ 19 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 20 | 21 | // window 生成 22 | window = UIWindow(frame: UIScreen.main.bounds) 23 | if let window = window { 24 | window.backgroundColor = UIColor.white 25 | // rootViewController の割り当て 26 | window.rootViewController = TitleViewController() 27 | window.makeKeyAndVisible() 28 | } 29 | 30 | return true 31 | } 32 | 33 | func applicationWillResignActive(_ application: UIApplication) { 34 | // 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. 35 | // 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. 36 | } 37 | 38 | func applicationDidEnterBackground(_ application: UIApplication) { 39 | // 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. 40 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 41 | } 42 | 43 | func applicationWillEnterForeground(_ application: UIApplication) { 44 | // 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. 45 | } 46 | 47 | func applicationDidBecomeActive(_ application: UIApplication) { 48 | // 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. 49 | } 50 | 51 | func applicationWillTerminate(_ application: UIApplication) { 52 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 53 | } 54 | 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /SwiftRPG/src/Controller/GameViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameViewController.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2015/06/27. 6 | // Copyright (c) 2015年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpriteKit 11 | import SwiftyJSON 12 | import PromiseKit 13 | 14 | class GameViewController: UIViewController { 15 | var viewInitiated: Bool = false 16 | var eventManager: EventManager! 17 | var eventObjectIds: Set? = nil 18 | var currentGameScene: GameScene? = nil 19 | 20 | override func loadView() { 21 | self.view = SKView() 22 | } 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | self.view.isMultipleTouchEnabled = false 27 | self.eventManager = EventManager() 28 | } 29 | 30 | override func viewWillLayoutSubviews() { 31 | super.viewWillLayoutSubviews() 32 | 33 | if (!viewInitiated) { 34 | let scene = FirstGameScene(size: self.view.frame.size, playerCoordiante: TileCoordinate(x:7,y:7), playerDirection: .down) 35 | scene.gameSceneDelegate = self 36 | scene.container = self.eventManager 37 | self.currentGameScene = scene 38 | 39 | let skView = self.view as! SKView 40 | skView.presentScene(self.currentGameScene) 41 | 42 | self.viewInitiated = true 43 | } 44 | } 45 | } 46 | 47 | extension GameViewController: GameSceneDelegate { 48 | func frameTouched(_ location: CGPoint) {} 49 | 50 | func gameSceneTouched(_ location: CGPoint) { 51 | let args = JSON(["touchedPoint": NSStringFromCGPoint(location)]) 52 | let gameScene = self.currentGameScene! 53 | 54 | do { 55 | try self.eventManager.trigger(.touch, sender: gameScene, args: args) 56 | } catch EventManagerError.FailedToTrigger(let string) { 57 | print("Failed to trigger touch event: " + string) 58 | } catch { 59 | print("Unexpected error has occurred during triggering touch event") 60 | } 61 | } 62 | 63 | func actionButtonTouched() { 64 | let gameScene = self.currentGameScene! 65 | 66 | do { 67 | try self.eventManager.trigger(.button, sender: gameScene, args: nil) 68 | } catch EventManagerError.FailedToTrigger(let string) { 69 | print("Failed to trigger action event: " + string) 70 | } catch { 71 | print("Unexpected error has occurred during triggering action event") 72 | } 73 | } 74 | 75 | func menuButtonTouched() { 76 | let viewController = MenuViewController() 77 | self.present(viewController, animated: true, completion: nil) 78 | } 79 | 80 | // TODO: Error handling when adding or removing event listener has failed 81 | 82 | // This function is executed cyclically 83 | // The role of this function is as following 84 | // - Update object's z-index position. 85 | // Front objects should render as looking like upper than back objects. 86 | // - Check player collision with events. 87 | // If the collision was occurred, invoke event. 88 | // - Trigger cyclic event listeners. 89 | func viewUpdated() { 90 | let gameScene = self.currentGameScene! 91 | let map = gameScene.map 92 | 93 | // Update z-index of objects 94 | map?.updateObjectsZPosition() 95 | 96 | // If player was on the event object, the listeners which has the placed event's 97 | // id should be in event dispatcher. But if player left from the event object, 98 | // the listeners should be removed from dispatcher as soon as possible. 99 | // To realize above, the placed event's id should be stored in somewhere. 100 | // 101 | // - The role of Object class is to store information about self and generate some 102 | // components (e.g. animation) for dealing with self by others. 103 | // - The role of Map class is to store the state of placement of objects and tiles, 104 | // and provide methods for manipulating them. 105 | // 106 | // I cannot judge this, so this role add to this controller for now. 107 | let events_ = map?.getEventsOnPlayerPosition() 108 | if events_ != nil && map?.getObjectByName(objectNameTable.PLAYER_NAME)?.isAnimated == false { 109 | let events: [EventListener] = events_! 110 | 111 | // Update eventObjectIds value 112 | if self.eventObjectIds == nil { 113 | self.eventObjectIds = [] 114 | for event in events { 115 | self.eventObjectIds?.insert(event.eventObjectId!) 116 | } 117 | } else { 118 | var newIdSets: Set = [] 119 | for event in events { 120 | newIdSets.insert(event.eventObjectId!) 121 | } 122 | let unregisteredIds = newIdSets.subtracting(self.eventObjectIds!) 123 | for id in unregisteredIds { 124 | self.eventObjectIds?.insert(id) 125 | } 126 | let removedIds = self.eventObjectIds?.subtracting(newIdSets) 127 | for id in removedIds! { 128 | self.eventManager.remove(id, sender: gameScene) 129 | self.eventObjectIds!.remove(id) 130 | } 131 | } 132 | 133 | // Invoke events 134 | for event in events { 135 | self.eventManager.add(event) 136 | } 137 | } else { 138 | if self.eventObjectIds != nil { 139 | for id in self.eventObjectIds! { 140 | self.eventManager.remove(id, sender: gameScene) 141 | } 142 | self.eventObjectIds = nil 143 | } 144 | } 145 | 146 | // Trigger cyclic events 147 | do { 148 | try self.eventManager.trigger(.immediate, sender: gameScene, args: nil) 149 | } catch EventManagerError.FailedToTrigger(let string) { 150 | print("Failed to trigger cyclic event: " + string) 151 | } catch { 152 | print("Unexpected error has occurred during triggering cyclic event") 153 | } 154 | } 155 | 156 | func startBehaviors(_ behaviors: Dictionary) { 157 | if self.eventManager.isBlockingBehavior == false { return } 158 | 159 | self.eventManager.unblockBehavior() 160 | for behavior in behaviors.values { 161 | behavior.isExecuting = false 162 | self.eventManager.add(behavior) 163 | } 164 | } 165 | 166 | func stopBehaviors() { 167 | self.eventManager.blockBehavior() 168 | } 169 | 170 | func startWalking() { 171 | if self.eventManager.isBlockingWalking == false { return } 172 | 173 | self.eventManager.unblockWalking() 174 | self.eventManager.add(WalkEventListener.init(params: nil, chainListeners: nil)) 175 | } 176 | 177 | func stopWalking() { 178 | self.eventManager.blockWalking() 179 | } 180 | 181 | func unavailableAllListeners() { 182 | self.eventManager.unavailableAllListeners() 183 | } 184 | 185 | func transitionTo(_ newScene: GameScene.Type, playerCoordinate coordinate: TileCoordinate, playerDirection direction: DIRECTION) -> Promise { 186 | let scene = newScene.init(size: self.view.bounds.size, playerCoordiante: coordinate, playerDirection: direction) 187 | scene.gameSceneDelegate = self 188 | scene.container = self.eventManager 189 | self.currentGameScene = scene 190 | 191 | let delay: TimeInterval = 2 192 | let skView = self.view as! SKView 193 | let skScene = skView.scene! 194 | skScene.view?.presentScene(scene, transition: SKTransition.fade(withDuration: delay)) 195 | 196 | return Promise { fulfill, reject in 197 | UIView.animate( 198 | withDuration: delay, 199 | animations: { () -> Void in } 200 | ) { (animationCompleted: Bool) -> Void in fulfill()} 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /SwiftRPG/src/Controller/MenuViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuViewController.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/12/21. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpriteKit 11 | 12 | class MenuViewController: UIViewController { 13 | var viewInitiated: Bool = false 14 | fileprivate var model: MenuSceneModel! 15 | let transition = TransitionBetweenGameAndMenuSceneAnimator() 16 | 17 | override func loadView() { 18 | self.view = SKView() 19 | } 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | self.view.isMultipleTouchEnabled = false 24 | } 25 | 26 | override func viewWillLayoutSubviews() { 27 | super.viewWillLayoutSubviews() 28 | 29 | if (!viewInitiated) { 30 | let scene = MenuScene(size: self.view.bounds.size) 31 | scene.menuSceneDelegate = self 32 | 33 | self.model = MenuSceneModel() 34 | self.model.delegate = scene 35 | self.model.updateItems() 36 | scene.model = self.model 37 | 38 | self.view = scene.sceneView 39 | let skView = self.view as! SKView 40 | skView.presentScene(scene) 41 | 42 | self.viewInitiated = true 43 | } 44 | } 45 | } 46 | 47 | extension MenuViewController: MenuSceneDelegate { 48 | func didPressBackButton() { 49 | self.dismiss(animated: true, completion: nil) 50 | } 51 | 52 | func didSelectedItem(_ indexPath: IndexPath) { 53 | self.model.selectItem(indexPath) 54 | } 55 | } 56 | 57 | extension MenuViewController: UIViewControllerTransitioningDelegate { 58 | func animationController( 59 | forPresented presented: UIViewController, 60 | presenting: UIViewController, 61 | source: UIViewController 62 | ) -> UIViewControllerAnimatedTransitioning? 63 | { 64 | self.transition.originFrame = self.view.frame 65 | self.transition.presenting = true 66 | return self.transition 67 | } 68 | 69 | func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 70 | self.transition.presenting = false 71 | return self.transition 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /SwiftRPG/src/Controller/TitleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TitleViewController.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2015/07/15. 6 | // Copyright (c) 2015年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpriteKit 11 | 12 | class TitleViewController: UIViewController { 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | let view = TitleScene(frame: self.view.frame) 16 | view.titleSceneDelegate = self 17 | 18 | self.view.addSubview(view) 19 | } 20 | 21 | override func didReceiveMemoryWarning() { 22 | super.didReceiveMemoryWarning() 23 | } 24 | } 25 | 26 | extension TitleViewController: TitleSceneDelegate { 27 | func newGameTouched() { 28 | let gameViewController: UIViewController = GameViewController() 29 | self.present(gameViewController, animated: false, completion: nil) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/A-star/AStar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AStar.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2015/08/10. 6 | // Copyright © 2015年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SpriteKit 12 | 13 | class AStar { 14 | /// 基準ノードのインデックス 15 | var iBaseNode: Int! 16 | 17 | /// 探索対象のマップ 18 | let map: Map 19 | 20 | /// 出発地点のタイル座標 21 | var departure: TileCoordinate! 22 | 23 | /// 目的地点のタイル座標 24 | var destination: TileCoordinate! 25 | 26 | /// 生成したノードを格納しておくリスト 27 | var nodeList: [Node] = [] 28 | 29 | init(map: Map) { 30 | self.map = map 31 | } 32 | 33 | /// A*アルゴリズムの初期化 34 | /// 35 | /// - parameter departure: 出発するタイル座標 36 | /// - parameter destination: 目的地のタイル座標 37 | func initialize(_ departure: TileCoordinate, destination: TileCoordinate) { 38 | self.departure = departure 39 | self.destination = destination 40 | nodeList = [] 41 | } 42 | 43 | /// A*アルゴリズム開始 44 | /// 45 | /// - returns: 失敗の場合はnil,成功の場合は経路を表したタイルの配列が返る 46 | func main() -> [TileCoordinate]? { 47 | /*** 終了判定 ***/ 48 | // initialize されていない 49 | if departure == nil || destination == nil { 50 | print("A* is not initialized") 51 | return nil 52 | } 53 | // 目標のタイルが到達不可能,もしくは通行不可能 54 | if !canPass(destination) { 55 | print("Target tile cannnot pass or reach") 56 | return nil 57 | } 58 | 59 | // 基準ノードの取得 60 | if nodeList.isEmpty { 61 | iBaseNode = 0 62 | nodeList.append(Node(coordinates: departure)) 63 | nodeList[iBaseNode].open(nil, destination: destination) 64 | } else { 65 | iBaseNode = chooseBaseNodeIndex() 66 | } 67 | // 基準ノードが存在しない場合は,移動失敗 68 | if iBaseNode == nil { 69 | print("There are no base node") 70 | return nil 71 | } 72 | 73 | // 基準ノードの周囲からOpen可能なノードを探す 74 | let indexes = searchCanOpenNodeIndexes(iBaseNode) 75 | // 各ノードをOpenする 76 | for index in indexes { 77 | nodeList[index].open(nodeList[iBaseNode], destination: destination) 78 | // 終了判定 79 | if nodeList[index].coordinates == destination { 80 | return getAStarResult() 81 | } 82 | } 83 | // 基準ノードを閉じる 84 | nodeList[iBaseNode].close() 85 | 86 | return main() 87 | } 88 | 89 | /// 基準ノードのインデックスを選ぶ 90 | /// 91 | /// - returns: 基準ノードのインデックス 92 | fileprivate func chooseBaseNodeIndex() -> Int? { 93 | var min = -1 94 | var iMinNode: Int? = nil 95 | 96 | // Open なノードを選ぶ 97 | for i in 0 ..< nodeList.count { 98 | if nodeList[i].state == Node.STATE.open { 99 | // スコアが最小のものを選ぶ 100 | if min == -1 { 101 | min = nodeList[i].score 102 | iMinNode = i 103 | continue 104 | } 105 | if nodeList[i].score < min { 106 | min = nodeList[i].score 107 | iMinNode = i 108 | } 109 | } 110 | } 111 | return iMinNode 112 | } 113 | 114 | /// 基準ノードの周りの Open 可能なノードを探す 115 | /// 116 | /// - parameter iBaseNode: 基準ノードのインデックス 117 | /// 118 | /// - returns: open 可能なノードのインデックス 119 | fileprivate func searchCanOpenNodeIndexes(_ iBaseNode: Int) -> [Int] { 120 | var checkCoordinates: [TileCoordinate] = [] 121 | var indexes: [Int] = [] 122 | let baseX = nodeList[iBaseNode].coordinates.x 123 | let baseY = nodeList[iBaseNode].coordinates.y 124 | 125 | // 基準ノードの上下左右のノードを調べる 126 | checkCoordinates.append(TileCoordinate(x: baseX - 1, y: baseY)) 127 | checkCoordinates.append(TileCoordinate(x: baseX + 1, y: baseY)) 128 | checkCoordinates.append(TileCoordinate(x: baseX, y: baseY - 1)) 129 | checkCoordinates.append(TileCoordinate(x: baseX, y: baseY + 1)) 130 | 131 | for coordinate in checkCoordinates { 132 | // 通行不可ならば,無視する 133 | if !canPass(coordinate) { 134 | continue 135 | } 136 | // ノードリストにノードとして存在するか 137 | let i_node = nodeList.index() { 138 | $0.coordinates == coordinate 139 | } 140 | if (i_node != nil) { 141 | if (nodeList[i_node!].state == Node.STATE.none) { 142 | indexes.append(i_node!) 143 | } 144 | } else { 145 | // ノードを新たに生成・追加 146 | let new_node = Node(coordinates: coordinate) 147 | nodeList.append(new_node) 148 | indexes.append(nodeList.count - 1) 149 | } 150 | } 151 | 152 | return indexes 153 | } 154 | 155 | /// 探索結果を取得する 156 | /// 157 | /// - returns: 移動経路を表すタイル座標の配列 158 | fileprivate func getAStarResult() -> [TileCoordinate] { 159 | var result: [TileCoordinate] = [] 160 | var node: Node 161 | 162 | let index = nodeList.index() { 163 | $0.coordinates == destination 164 | } 165 | node = nodeList[index!] 166 | while node.parentNode != nil { 167 | result.append(node.coordinates) 168 | node = node.parentNode! 169 | } 170 | 171 | return result.reversed() 172 | } 173 | 174 | /// タイルの通行判定 175 | /// 176 | /// - parameter coordinate: タイルの座標 177 | /// 178 | /// - returns: 通行可能なら true, そうでなければ false 179 | fileprivate func canPass(_ coordinate: TileCoordinate) -> Bool { 180 | return self.map.canPass(coordinate) 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/A-star/Node.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Node.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2015/08/08. 6 | // Copyright © 2015年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SpriteKit 12 | 13 | /// A*アルゴリズムのためのノード 14 | class Node { 15 | 16 | enum STATE { 17 | case none, open, closed 18 | } 19 | 20 | /// ステータス 21 | fileprivate(set) var state: STATE! 22 | 23 | /// XY座標 24 | fileprivate(set) var coordinates: TileCoordinate! 25 | 26 | /// 親ノード 27 | fileprivate(set) var parentNode: Node! 28 | 29 | /// 推定コスト 30 | fileprivate var heuristicCost: Int! 31 | 32 | /// 移動コスト 33 | fileprivate(set) var moveCost: Int! 34 | 35 | var score: Int { 36 | get { 37 | return self.heuristicCost + self.moveCost 38 | } 39 | } 40 | 41 | /// コンストラクタ 42 | /// 43 | /// - parameter coordinates: タイル座標 44 | init(coordinates: TileCoordinate) { 45 | self.state = STATE.none 46 | self.coordinates = TileCoordinate(x: coordinates.x, 47 | y: coordinates.y) 48 | } 49 | 50 | /// ノードを開く 51 | /// 52 | /// - parameter parentNode: 親ノード 53 | /// - parameter destination: 目的地のタイル座標 54 | func open(_ parentNode: Node?, destination: TileCoordinate) { 55 | // ノードをOpen状態にする 56 | self.state = STATE.open 57 | 58 | // 実コストを求める. スタート地点だった場合には 0 59 | if parentNode == nil { 60 | moveCost = 0 61 | } else { 62 | moveCost = parentNode!.moveCost + 1 63 | } 64 | 65 | // 推定コストを求める 66 | let dx = abs(destination.x - coordinates.x) 67 | let dy = abs(destination.y - coordinates.y) 68 | heuristicCost = dx + dy 69 | 70 | // 親ノードを保持する 71 | self.parentNode = parentNode 72 | } 73 | 74 | /// ノードを閉じる 75 | func close() { 76 | self.state = STATE.closed 77 | } 78 | 79 | /// ノードの現在位置を確認する 80 | /// 81 | /// - parameter coordinates: 確認する座標 82 | /// 83 | /// - returns: 指定した座標にノードが存在しなければ false, 存在すれば true 84 | func isPositioned(_ coordinates: TileCoordinate) -> Bool { 85 | if coordinates == coordinates { 86 | return true 87 | } else { 88 | return false 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/DataModel/DataModels.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataModels.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/08/04. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Realm 11 | import RealmSwift 12 | 13 | class StoredItems: RealmSwift.Object { 14 | dynamic var key: String = "" 15 | dynamic var name: String = "" 16 | dynamic var text: String = "" 17 | dynamic var image_name: String = "" 18 | dynamic var num: Int = 0 19 | 20 | override static func primaryKey() -> String? { 21 | return "key" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Dialog/TalkBodyParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TalkBodyParser.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2016/02/26. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | class TalkBodyParser { 13 | fileprivate var body: String! 14 | 15 | init?(talkFileName: String) { 16 | if let path: String = Bundle.main.path(forResource: talkFileName, ofType: nil), 17 | let fileHandle: FileHandle = FileHandle(forReadingAtPath: path) 18 | { 19 | let data: Data = fileHandle.readDataToEndOfFile() 20 | self.body = NSString(data:data, encoding:String.Encoding.utf8.rawValue) as! String 21 | } else { 22 | self.body = nil 23 | return nil 24 | } 25 | } 26 | 27 | /// パース状態 28 | /// 29 | /// - CONFIG: プレイヤー情報読み込み 30 | /// - BODY: 会話内容読み込み 31 | fileprivate enum PARSING { 32 | case config 33 | case body 34 | } 35 | 36 | func parse() -> JSON { 37 | var index: Int = 0 38 | var state: PARSING = .config 39 | var didReadBody = false 40 | var talksInfo = [[String: String]]() 41 | var talkInfo = [String: String]() 42 | var tmpTalkBody: String = "" 43 | 44 | self.body.enumerateLines { 45 | (line, stop) -> () in 46 | 47 | if line == "!" { 48 | talkInfo["talk_body"] = tmpTalkBody 49 | talksInfo.append(talkInfo) 50 | tmpTalkBody = "" 51 | index += 1 52 | state = .config 53 | didReadBody = false 54 | return 55 | } 56 | 57 | switch state { 58 | case .config: 59 | var config = line.characters.split(separator: ":").map{ String($0) } 60 | talkInfo["talker"] = config[0] 61 | talkInfo["talk_side"] = config[1] 62 | state = .body 63 | break 64 | case .body: 65 | if didReadBody { 66 | tmpTalkBody.append(line) 67 | return 68 | } 69 | tmpTalkBody.append(Dialog.NEWLINE_CHAR) 70 | tmpTalkBody.append(line) 71 | break 72 | } 73 | } 74 | 75 | return JSON(talksInfo) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventDispatcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Event.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2015/08/12. 6 | // Copyright © 2015年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import UIKit 12 | import PromiseKit 13 | 14 | enum EventDispacherError: Error { 15 | case FiledToInvokeListener(String) 16 | } 17 | 18 | protocol NotifiableFromListener { 19 | func invoke(_ listener: EventListener, invoker: EventListener) 20 | } 21 | 22 | class EventDispatcher : NotifiableFromListener { 23 | typealias ListenerType = EventListener 24 | typealias IdType = UInt64 25 | 26 | fileprivate var listeners = Dictionary() 27 | fileprivate var uniqueId: UInt64 = 0 28 | 29 | let triggerType: TriggerType 30 | 31 | var delegate: NotifiableFromDispacher? 32 | 33 | init(_ type: TriggerType) { 34 | self.triggerType = type 35 | } 36 | 37 | @discardableResult 38 | func add(_ listener: ListenerType) -> Bool { 39 | if listener.id != nil { return false } 40 | let id = issueId() 41 | listener.delegate = self 42 | listeners[id] = listener 43 | listener.id = id 44 | return true 45 | } 46 | 47 | @discardableResult 48 | func remove(_ listener: ListenerType, sender: GameSceneProtocol? = nil) -> Bool { 49 | if listener.id == nil { return false } 50 | 51 | do { 52 | try listener.rollback?(sender, nil).catch { error in 53 | // TODO 54 | } 55 | } catch { 56 | // TODO 57 | } 58 | 59 | let id = listener.id! 60 | listeners.removeValue(forKey: id) 61 | listener.id = nil 62 | self.delegate?.removed(id, sender: self) 63 | 64 | return true 65 | } 66 | 67 | func getAllListeners() -> [EventListener] { 68 | var listeners: [EventListener] = [] 69 | for listener in self.listeners.values { 70 | listeners.append(listener) 71 | } 72 | return listeners 73 | } 74 | 75 | // Invoke all event listenrs in this dispacher. 76 | // If exception has thrown during executing, remove the listener which thrown exception. 77 | func trigger(_ sender: GameSceneProtocol!, args: JSON!) throws { 78 | for listener in listeners.values { 79 | if !listener.isExecuting { 80 | try listener.invoke!(sender, args).then { _ -> Void in 81 | // TODO: 82 | // Should use remove() function 83 | // But the function execute rollback() 84 | // Need to prepare different remove function which is not executing rollback() 85 | if let id_ = listener.id { 86 | self.listeners.removeValue(forKey: id_) 87 | listener.id = nil 88 | self.delegate?.removed(id_, sender: self) 89 | } else { 90 | // If the listener was removed before removing in this block, here is executed 91 | print("Failed to listener at the time of end of trigger") 92 | } 93 | }.catch { error in 94 | // TODO: 95 | } 96 | } 97 | } 98 | } 99 | 100 | func hasListener() -> Bool { 101 | return self.listeners.count > 0 102 | } 103 | 104 | fileprivate func issueId() -> IdType { 105 | repeat { 106 | uniqueId += 1 107 | if listeners[uniqueId] == nil { 108 | return uniqueId 109 | } 110 | } while (true) // ToDo 111 | } 112 | 113 | // MARK: NotifilableFromListener 114 | 115 | func invoke(_ nextListener: EventListener, invoker: EventListener) { 116 | self.delegate?.invoke(nextListener, invoker: invoker) 117 | } 118 | } 119 | 120 | 121 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventListener.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2015/09/04. 6 | // Copyright © 2015年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import SpriteKit 12 | import PromiseKit 13 | 14 | enum EventListenerError: Error { 15 | case illegalArguementFormat(String) 16 | case illegalParamFormat([String]) 17 | case invalidParam(String) 18 | } 19 | 20 | enum TriggerType { 21 | case touch 22 | case immediate 23 | case button 24 | } 25 | 26 | protocol GameSceneProtocol { 27 | init(size: CGSize, playerCoordiante: TileCoordinate, playerDirection: DIRECTION) 28 | var playerInitialCoordinate: TileCoordinate? { get } 29 | var playerInitialDirection: DIRECTION? { get } 30 | 31 | var actionButton: SKSpriteNode { get set } 32 | var menuButton: SKSpriteNode { get set } 33 | var eventDialog: SKSpriteNode { get set } 34 | var actionButtonLabel: SKLabelNode { get set } 35 | var menuButtonLabel: SKLabelNode { get set } 36 | var eventDialogLabel: SKLabelNode { get set } 37 | var map: Map? { get set } 38 | var textBox: Dialog! { get set } 39 | 40 | func movePlayer(_ actions: [SKAction], departure: TileCoordinate, destination: TileCoordinate, screenAction: SKAction, invoker: EventListener) 41 | -> Promise 42 | func moveObject(_ name: String, actions: [SKAction], departure: TileCoordinate, destination: TileCoordinate, invoker: EventListener) 43 | -> Promise 44 | func hideAllButtons() -> Promise 45 | func showDefaultButtons() -> Promise 46 | func showEventDialog() -> Promise 47 | 48 | func stopBehaviors() 49 | func startBehaviors() 50 | func enableWalking() 51 | func disableWalking() 52 | func removeAllEvetListenrs() 53 | 54 | func enableTouchEvents() 55 | func disableTouchEvents() 56 | 57 | func transitionTo(_ newScene: GameScene.Type, playerCoordinate: TileCoordinate, playerDirection: DIRECTION) -> Promise 58 | } 59 | 60 | typealias EventMethod = (_ sender: GameSceneProtocol?, _ args: JSON?) throws -> Promise 61 | protocol EventHandler: class { 62 | var invoke: EventMethod? { get set } 63 | var rollback: EventMethod? { get set } 64 | var triggerType: TriggerType { get } 65 | } 66 | 67 | typealias ListenerChain = [(listener: EventListener.Type, params: JSON?)] 68 | protocol EventListener: EventHandler { 69 | var id: UInt64! { get set } 70 | var delegate: NotifiableFromListener? { get set } 71 | var listeners: ListenerChain? { get } 72 | var isExecuting: Bool { get set } 73 | var isBehavior: Bool { get set } 74 | var eventObjectId: MapObjectId? { get set } 75 | 76 | func chain(listeners: ListenerChain) 77 | init(params: JSON?, chainListeners: ListenerChain?) throws 78 | } 79 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/ActivateButtonListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActivateButtonListener.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/08/06. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import JSONSchema 12 | import SpriteKit 13 | import PromiseKit 14 | 15 | class ActivateButtonListener: EventListenerImplement { 16 | required init(params: JSON?, chainListeners listeners: ListenerChain?) throws { 17 | try! super.init(params: params, chainListeners: listeners) 18 | 19 | let schema = Schema([ 20 | "type": "object", 21 | "properties": [ 22 | "text": ["type": "string"], 23 | ], 24 | "required": ["text"], 25 | ]) 26 | let result = schema.validate(params?.rawValue ?? []) 27 | if result.valid == false { 28 | throw EventListenerError.illegalParamFormat(result.errors!) 29 | } 30 | 31 | self.triggerType = .immediate 32 | self.invoke = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 33 | sender!.actionButtonLabel.text = self.params!["text"].string! 34 | sender!.actionButton.isHidden = false 35 | 36 | sender?.stopBehaviors() 37 | 38 | do { 39 | let nextEventListener = try InvokeNextEventListener(params: self.params, chainListeners: self.listeners) 40 | nextEventListener.eventObjectId = self.eventObjectId 41 | nextEventListener.isBehavior = self.isBehavior 42 | self.delegate?.invoke(nextEventListener, invoker: self) 43 | } catch { 44 | throw error 45 | } 46 | 47 | return Promise { fullfill, reject in fullfill() } 48 | } 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/BackToDefaultStateEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnableWalkingEventListener.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2017/01/29. 6 | // Copyright © 2017年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import JSONSchema 12 | import SpriteKit 13 | import PromiseKit 14 | 15 | class BackToDefaultStateEventListener: EventListenerImplement { 16 | required init(params: JSON?, chainListeners listeners: ListenerChain?) throws { 17 | try! super.init(params: params, chainListeners: listeners) 18 | self.triggerType = .immediate 19 | self.invoke = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 20 | 21 | sender!.enableWalking() 22 | sender!.startBehaviors() 23 | sender!.menuButton.isHidden = false 24 | 25 | return Promise { fullfill, reject in fullfill() } 26 | } 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/Event Dialog/HideEventDialogListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HideEventDialogListener.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2017/01/26. 6 | // Copyright © 2017年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import JSONSchema 12 | import SpriteKit 13 | import PromiseKit 14 | 15 | class HideEventDialogListener: EventListenerImplement { 16 | required init(params: JSON?, chainListeners listeners: ListenerChain?) { 17 | try! super.init(params: params, chainListeners: listeners) 18 | 19 | self.triggerType = .touch 20 | self.rollback = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 21 | sender?.eventDialog.isHidden = true 22 | return Promise { fullfill, reject in fullfill() } 23 | } 24 | self.invoke = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 25 | sender!.eventDialog.isHidden = true 26 | 27 | do { 28 | let nextEventListener = try InvokeNextEventListener(params: self.params, chainListeners: self.listeners) 29 | nextEventListener.eventObjectId = self.eventObjectId 30 | nextEventListener.isBehavior = self.isBehavior 31 | self.delegate?.invoke(nextEventListener, invoker: self) 32 | } catch { 33 | throw error 34 | } 35 | 36 | return Promise { fullfill, reject in fullfill() } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/Event Dialog/ShowEventDialogListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActivateEventDialogListener.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2017/01/23. 6 | // Copyright © 2017年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import JSONSchema 12 | import SpriteKit 13 | import PromiseKit 14 | 15 | class ShowEventDialogListener: EventListenerImplement { 16 | required init(params: JSON?, chainListeners listeners: ListenerChain?) throws { 17 | try! super.init(params: params, chainListeners: listeners) 18 | 19 | let schema = Schema([ 20 | "type": "object", 21 | "properties": [ 22 | "text": ["type": "string"], 23 | ], 24 | "required": ["text"], 25 | ]) 26 | let result = schema.validate(params?.rawValue ?? []) 27 | if result.valid == false { 28 | throw EventListenerError.illegalParamFormat(result.errors!) 29 | } 30 | 31 | self.triggerType = .immediate 32 | self.rollback = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 33 | sender?.eventDialog.isHidden = true 34 | return Promise { fullfill, reject in fullfill() } 35 | } 36 | self.invoke = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 37 | sender!.eventDialogLabel.text = params!["text"].string! 38 | sender!.eventDialog.isHidden = false 39 | 40 | // Stop All Object's behavior 41 | sender?.stopBehaviors() 42 | sender?.disableWalking() 43 | 44 | let nextEventListener = HideEventDialogListener(params: self.params, chainListeners: self.listeners) 45 | nextEventListener.eventObjectId = self.eventObjectId 46 | nextEventListener.isBehavior = self.isBehavior 47 | self.delegate?.invoke(nextEventListener, invoker: self) 48 | 49 | return Promise { fullfill, reject in fullfill() } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/EventListenerImplement.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventListenerImplement.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2017/01/29. 6 | // Copyright © 2017年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import JSONSchema 12 | import SpriteKit 13 | import PromiseKit 14 | 15 | class EventListenerImplement: EventListener { 16 | var id: UInt64! 17 | var delegate: NotifiableFromListener? = nil 18 | var invoke: EventMethod? = nil 19 | var rollback: EventMethod? = nil 20 | var listeners: ListenerChain? = nil 21 | var params: JSON? = nil 22 | var eventObjectId: MapObjectId? = nil 23 | var isExecuting: Bool = false 24 | var isBehavior: Bool = false 25 | var triggerType: TriggerType 26 | 27 | required init(params: JSON?, chainListeners listeners: ListenerChain?) throws { 28 | self.params = params 29 | self.listeners = listeners 30 | self.triggerType = .immediate 31 | } 32 | 33 | internal func chain(listeners: ListenerChain) { 34 | self.listeners = listeners 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/InvokeNextEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InvokeNextEventListener.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/12/23. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import PromiseKit 12 | 13 | class InvokeNextEventListener: EventListenerImplement { 14 | required init(params: JSON?, chainListeners listeners: ListenerChain?) throws { 15 | try! super.init(params: params, chainListeners: listeners) 16 | 17 | self.triggerType = .immediate 18 | self.invoke = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 19 | // If there are no registered listener, exit 20 | if listeners == nil || listeners?.count == 0 { 21 | return Promise { fullfill, reject in fullfill() } 22 | } 23 | 24 | // If there are registered listener, invoke it 25 | let nextListener = listeners!.first!.listener 26 | let nextListenerChain: ListenerChain? = listeners!.count == 1 ? nil : Array(listeners!.dropFirst()) 27 | do { 28 | let nextListenerInstance = try nextListener.init(params: listeners!.first!.params, chainListeners: nextListenerChain) 29 | nextListenerInstance.eventObjectId = self.eventObjectId 30 | nextListenerInstance.isBehavior = self.isBehavior 31 | self.delegate?.invoke(nextListenerInstance, invoker: self) 32 | } catch { 33 | throw error 34 | } 35 | 36 | return Promise { fullfill, reject in fullfill() } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/ItemGetEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemGetEvent.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/08/04. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import JSONSchema 12 | import SpriteKit 13 | import RealmSwift 14 | import PromiseKit 15 | 16 | class ItemGetEventListener: EventListener { 17 | var id: UInt64! 18 | var delegate: NotifiableFromListener? 19 | var invoke: EventMethod? 20 | var rollback: EventMethod? 21 | var isExecuting: Bool = false 22 | var isBehavior: Bool = false 23 | var eventObjectId: MapObjectId? = nil 24 | let triggerType: TriggerType 25 | 26 | internal var listeners: ListenerChain? 27 | fileprivate let params: JSON 28 | fileprivate let itemKey: String 29 | fileprivate let itemName: String 30 | fileprivate let itemText: String 31 | fileprivate let itemImageName: String 32 | 33 | required init(params: JSON?, chainListeners listeners: ListenerChain?) throws { 34 | 35 | let schema = Schema([ 36 | "type": "object", 37 | "properties": [ 38 | "key": ["type": "string"], 39 | "name": ["type": "string"], 40 | "description": ["type": "string"], 41 | "image_name": ["type": "string"], 42 | ], 43 | "required": ["key", "name", "description", "image_name"], 44 | ]) 45 | let result = schema.validate(params?.rawValue ?? []) 46 | if result.valid == false { 47 | throw EventListenerError.illegalParamFormat(result.errors!) 48 | } 49 | 50 | self.triggerType = .immediate 51 | self.params = params! 52 | self.listeners = listeners 53 | self.itemKey = params!["key"].string! 54 | self.itemName = params!["name"].string! 55 | self.itemText = params!["description"].string! 56 | self.itemImageName = params!["image_name"].string! 57 | self.invoke = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 58 | // Insert data to database 59 | let realm = try! Realm() 60 | try! realm.write { 61 | var item = realm.objects(StoredItems.self).filter("key == \"\(self.itemKey)\"").first 62 | if item != nil { 63 | item!.num += 1 64 | } else { 65 | item = StoredItems() 66 | item!.key = self.itemKey 67 | item!.name = self.itemName 68 | item!.text = self.itemText 69 | item!.image_name = self.itemImageName 70 | } 71 | realm.add(item!, update: true) 72 | } 73 | 74 | do { 75 | let nextEventListener = try InvokeNextEventListener(params: self.params, chainListeners: self.listeners) 76 | nextEventListener.eventObjectId = self.eventObjectId 77 | nextEventListener.isBehavior = self.isBehavior 78 | self.delegate?.invoke(nextEventListener, invoker: self) 79 | } catch { 80 | throw error 81 | } 82 | 83 | return Promise { fullfill, reject in fullfill() } 84 | } 85 | } 86 | 87 | internal func chain(listeners: ListenerChain) { 88 | self.listeners = listeners 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/MoveObjectEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoveObjectEventListener.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2017/01/23. 6 | // Copyright © 2017年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SpriteKit 12 | import SwiftyJSON 13 | import JSONSchema 14 | import PromiseKit 15 | 16 | class MoveObjectEventListener: EventListenerImplement { 17 | required init(params: JSON?, chainListeners listeners: ListenerChain?) throws { 18 | try! super.init(params: params, chainListeners: listeners) 19 | 20 | let schema = Schema([ 21 | "type": "object", 22 | "properties": [ 23 | "name": ["type": "string"], 24 | "direction": [ 25 | "type":"string", 26 | "enum": ["LEFT", "RIGHT", "UP", "DOWN"] 27 | ], 28 | "step_num": ["type":"string"], 29 | "speed": ["type":"string"] 30 | ], 31 | "required": ["name", "direction", "step_num", "speed"], 32 | ]) 33 | let result = schema.validate(params?.rawValue ?? []) 34 | if result.valid == false { 35 | throw EventListenerError.illegalParamFormat(result.errors!) 36 | } 37 | // TODO: Validation as following must be executed as a part of validation by JSONSchema 38 | if (Int(params!["step_num"].string!) == nil) { 39 | throw EventListenerError.illegalParamFormat(["The parameter 'step_num' couldn't convert to integer"]) 40 | } 41 | if (Int(params!["speed"].string!) == nil) { 42 | throw EventListenerError.illegalParamFormat(["The parameter 'speed' couldn't convert to integer"]) 43 | } 44 | 45 | self.triggerType = .immediate 46 | self.invoke = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 47 | self.isExecuting = true 48 | 49 | let map = sender!.map! 50 | 51 | let objectName = self.params?["name"].string! 52 | let direction = DIRECTION.fromString((self.params?["direction"].string!)!) 53 | let step_num = Int((self.params?["step_num"].string!)!) 54 | let speed = Int((self.params?["speed"].string!)!) 55 | 56 | let object = map.getObjectByName(objectName!)! 57 | object.setDirection(direction!) 58 | object.setSpeed(CGFloat(speed!)) 59 | 60 | // Route search by A* algorithm 61 | let departure = object.coordinate 62 | let destination = self.calcDestination(departure, direction: direction!, step_num: step_num!) 63 | 64 | let aStar = AStar(map: map) 65 | aStar.initialize(departure, destination: destination) 66 | let route = aStar.main() 67 | if route == nil { 68 | // If there are no route to destination, finish this listener and invoke next listener 69 | do { 70 | let nextEventListener = try InvokeNextEventListener(params: self.params, chainListeners: self.listeners) 71 | nextEventListener.eventObjectId = self.eventObjectId 72 | nextEventListener.isBehavior = self.isBehavior 73 | self.delegate?.invoke(nextEventListener, invoker: self) 74 | } catch { 75 | throw error 76 | } 77 | return Promise { fullfill, reject in fullfill() } 78 | } 79 | 80 | // Disable events 81 | // Remove all events related to object from map 82 | map.removeEventsOfObject(object.id) 83 | 84 | // Define action for moving 85 | var objectActions: Array = [] 86 | var preStepCoordinate = object.coordinate 87 | var preStepPoint = object.position 88 | for nextStepCoordinate: TileCoordinate in route! { 89 | let nextStepPoint: CGPoint = TileCoordinate.getSheetCoordinateFromTileCoordinate(nextStepCoordinate) 90 | objectActions += object.getActionTo( 91 | preStepPoint, 92 | destination: nextStepPoint, 93 | preCallback: { 94 | map.setCollisionOn(coordinate: nextStepCoordinate) 95 | }, 96 | postCallback: { 97 | map.removeCollisionOn(coordinate: nextStepCoordinate) 98 | map.updateObjectPlacement(object, departure: preStepCoordinate, destination: nextStepCoordinate) 99 | } 100 | ) 101 | 102 | preStepCoordinate = nextStepCoordinate 103 | preStepPoint = nextStepPoint 104 | } 105 | 106 | return Promise { fullfill, reject in 107 | firstly { 108 | sender!.moveObject( 109 | objectName!, 110 | actions: objectActions, 111 | departure: departure, 112 | destination: destination, 113 | invoker: self 114 | ) 115 | }.then { _ -> Void in 116 | do { 117 | let nextEventListener = try InvokeNextEventListener(params: self.params, chainListeners: self.listeners) 118 | nextEventListener.eventObjectId = self.eventObjectId 119 | nextEventListener.isBehavior = self.isBehavior 120 | self.delegate?.invoke(nextEventListener, invoker: self) 121 | } catch { 122 | throw error 123 | } 124 | }.then { 125 | fullfill() 126 | }.catch { error in 127 | print(error.localizedDescription) 128 | } 129 | } 130 | } 131 | } 132 | 133 | fileprivate func calcDestination(_ departure: TileCoordinate, direction: DIRECTION, step_num: Int) -> TileCoordinate { 134 | var diff: TileCoordinate = TileCoordinate(x: 0, y: 0) 135 | switch direction { 136 | case .up: 137 | diff = diff + TileCoordinate(x: 0, y: 1) 138 | case .down: 139 | diff = diff + TileCoordinate(x: 0, y: -1) 140 | case .right: 141 | diff = diff + TileCoordinate(x: 1, y: 0) 142 | case .left: 143 | diff = diff + TileCoordinate(x: -1, y: 0) 144 | } 145 | return (departure + diff) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/ReloadBehaviorEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoadBehaviorEventListener.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2017/01/28. 6 | // Copyright © 2017年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import JSONSchema 12 | import SpriteKit 13 | import PromiseKit 14 | 15 | class ReloadBehaviorEventListener: EventListenerImplement { 16 | required init(params: JSON?, chainListeners listeners: ListenerChain?) throws { 17 | try! super.init(params: params, chainListeners: listeners) 18 | 19 | let schema = Schema([ 20 | "type": "object", 21 | "properties": [ 22 | "text": ["eventObjectId": "int"], 23 | ], 24 | "required": ["eventObjectId"], 25 | ]) 26 | let result = schema.validate(params?.rawValue ?? []) 27 | if result.valid == false { 28 | throw EventListenerError.illegalParamFormat(result.errors!) 29 | } 30 | if params?["eventObjectId"].int == nil { 31 | throw EventListenerError.illegalParamFormat(["parameter 'eventObjectId' cannot convert to int"]) 32 | } 33 | 34 | self.triggerType = .immediate 35 | self.invoke = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 36 | let map = sender!.map! 37 | let id = self.params?["eventObjectId"].int! 38 | 39 | // TODO: Implement utility function for generate listener from behavior listener chain 40 | var listener: EventListener? = nil 41 | let listenerChain = map.getObjectBehavior(id!) 42 | let listenerType = listenerChain?.first?.listener 43 | let params = listenerChain?.first?.params 44 | do { 45 | listener = try listenerType?.init( 46 | params: params, 47 | chainListeners: ListenerChain(listenerChain!.dropFirst(1))) 48 | listener?.eventObjectId = id 49 | listener?.isBehavior = true 50 | } catch { 51 | // TODO 52 | } 53 | 54 | self.delegate?.invoke(listener!, invoker: self) 55 | 56 | return Promise { fullfill, reject in fullfill() } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/RenderDefaultViewEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RenderDefaultViewEventListener.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2017/01/23. 6 | // Copyright © 2017年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import JSONSchema 12 | import SpriteKit 13 | import PromiseKit 14 | 15 | class RenderDefaultViewEventListener: EventListenerImplement { 16 | required init(params: JSON?, chainListeners listeners: ListenerChain?) throws { 17 | try! super.init(params: params, chainListeners: listeners) 18 | 19 | self.triggerType = .immediate 20 | self.invoke = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 21 | self.isExecuting = true 22 | 23 | return Promise { fullfill, reject in 24 | firstly { 25 | sender!.hideAllButtons() 26 | }.then { _ in 27 | sender!.showDefaultButtons() 28 | }.then { _ -> Void in 29 | do { 30 | let nextEventListener = try InvokeNextEventListener(params: self.params, chainListeners: self.listeners) 31 | nextEventListener.eventObjectId = self.eventObjectId 32 | nextEventListener.isBehavior = self.isBehavior 33 | self.delegate?.invoke(nextEventListener, invoker: self) 34 | } catch { 35 | throw error 36 | } 37 | }.then { 38 | fullfill() 39 | }.catch { error in 40 | print(error.localizedDescription) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/SceneTransitionEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneTransitionEventListener.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2017/01/29. 6 | // Copyright © 2017年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import JSONSchema 12 | import SpriteKit 13 | import PromiseKit 14 | 15 | class SceneTransitionEventListener: EventListenerImplement { 16 | required init(params: JSON?, chainListeners listeners: ListenerChain?) throws { 17 | try! super.init(params: params, chainListeners: listeners) 18 | 19 | let schema = Schema([ 20 | "type": "object", 21 | "properties": [ 22 | "map_file_name": ["type": "string"], 23 | "player_place_coordinate": ["type": "string"], 24 | "player_direction": [ 25 | "type":"string", 26 | "enum": ["LEFT", "RIGHT", "UP", "DOWN"] 27 | ] 28 | ], 29 | "required": ["map_file_name", "player_place_coordinate", "player_direction"], 30 | ]) 31 | let result = schema.validate(params?.rawValue ?? []) 32 | if result.valid == false { 33 | throw EventListenerError.illegalParamFormat(result.errors!) 34 | } 35 | 36 | self.triggerType = .immediate 37 | self.invoke = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 38 | let mapFileName = params!["map_file_name"].string! 39 | let playerCoordinate = self.convertToCoordinate(params!["player_place_coordinate"].string!) 40 | let playerDirection = DIRECTION.fromString((self.params?["player_direction"].string!)!) 41 | 42 | // Clean event manager: remove all event listener from event manager 43 | sender?.removeAllEvetListenrs() 44 | sender?.disableTouchEvents() 45 | 46 | // Scene transition 47 | let gameSceneType = MapTable.fromJsonFileName[mapFileName] 48 | 49 | return Promise { fullfill, reject in 50 | firstly { 51 | sender!.transitionTo(gameSceneType!, playerCoordinate: playerCoordinate!, playerDirection: playerDirection!) 52 | }.then { _ -> Void in 53 | sender!.enableTouchEvents() 54 | }.then { 55 | fullfill() 56 | }.catch { error in 57 | print(error.localizedDescription) 58 | } 59 | } 60 | } 61 | } 62 | 63 | fileprivate func convertToCoordinate(_ string: String) -> TileCoordinate? { 64 | let coordinates = string.components(separatedBy: "-") 65 | if let x = Int(coordinates[0]), 66 | let y = Int(coordinates[1]) { 67 | return TileCoordinate(x: x, y: y) 68 | } 69 | return nil 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/Talk Event/FinishTalkEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FinishTalkEventListener.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2017/01/26. 6 | // Copyright © 2017年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import JSONSchema 12 | import SpriteKit 13 | import PromiseKit 14 | 15 | class FinishTalkEventListener: EventListenerImplement { 16 | required init(params: JSON?, chainListeners listeners: ListenerChain?) throws { 17 | try! super.init(params: params, chainListeners: listeners) 18 | 19 | self.triggerType = .touch 20 | self.rollback = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 21 | sender?.actionButton.isHidden = true 22 | sender?.textBox.hide() 23 | return Promise { fullfill, reject in fullfill() } 24 | } 25 | self.invoke = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 26 | self.isExecuting = true 27 | 28 | sender!.textBox.clean() 29 | 30 | return Promise { fullfill, reject in 31 | firstly { 32 | sender!.textBox.hide(duration: 0) 33 | }.then { _ -> Void in 34 | do { 35 | let nextEventListener = try InvokeNextEventListener(params: self.params, chainListeners: self.listeners) 36 | nextEventListener.eventObjectId = self.eventObjectId 37 | nextEventListener.isBehavior = self.isBehavior 38 | self.delegate?.invoke(nextEventListener, invoker: self) 39 | } catch { 40 | throw error 41 | } 42 | }.then { 43 | fullfill() 44 | }.catch { error in 45 | print(error.localizedDescription) 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/Talk Event/StartTalkEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StartTalkEventListener.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2017/01/26. 6 | // Copyright © 2017年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import JSONSchema 12 | import SpriteKit 13 | import PromiseKit 14 | 15 | class StartTalkEventListener: EventListenerImplement { 16 | fileprivate var directionString: String 17 | 18 | required init(params: JSON?, chainListeners listeners: ListenerChain?) throws { 19 | if params == nil { throw EventListenerError.illegalParamFormat(["Parameter is nil"]) } 20 | 21 | let maxIndex = params?.arrayObject?.count 22 | if maxIndex == nil { throw EventListenerError.illegalParamFormat(["No properties in json parameter"]) } 23 | 24 | let directionString = params![maxIndex!-1]["direction"].string 25 | // Remove "direction" from params 26 | var array = params!.arrayObject as? [[String:String]] 27 | array?.removeLast() 28 | 29 | self.directionString = directionString! 30 | 31 | try! super.init(params: params, chainListeners: listeners) 32 | 33 | self.triggerType = .button 34 | self.params = JSON(array!) 35 | self.rollback = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 36 | sender?.actionButton.isHidden = true 37 | sender?.textBox.hide() 38 | sender?.startBehaviors() 39 | return Promise { fullfill, reject in fullfill() } 40 | } 41 | self.invoke = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 42 | self.isExecuting = true 43 | 44 | // Initialize dialog 45 | sender?.textBox.clean() 46 | 47 | // Stop All Object's behavior 48 | sender?.stopBehaviors() 49 | sender?.disableWalking() 50 | 51 | // Change direction of player 52 | let map = sender!.map! 53 | let player = map.getObjectByName(objectNameTable.PLAYER_NAME) 54 | if let playerDirection = DIRECTION.fromString(self.directionString) { 55 | player?.setDirection(playerDirection) 56 | } 57 | 58 | return Promise { fullfill, reject in 59 | firstly { 60 | sender!.hideAllButtons() 61 | }.then { 62 | sender!.textBox.show(duration: 0.2) 63 | }.then { _ -> Void in 64 | do { 65 | // Render next conversation 66 | let moveConversation = try TalkEventListener.generateMoveConversationMethod(0, params: self.params!) 67 | try moveConversation(_: sender, args).catch { error in 68 | // TODO 69 | } 70 | 71 | // TODO: If there are no need to invoke following (i.e. The conversation would finish in only one(above) step), 72 | // should deal with it well. 73 | let nextEventListener = try TalkEventListener(params: self.params, chainListeners: self.listeners) 74 | nextEventListener.eventObjectId = self.eventObjectId 75 | nextEventListener.isBehavior = self.isBehavior 76 | self.delegate?.invoke(nextEventListener, invoker: self) 77 | } catch { 78 | throw error 79 | } 80 | }.then { 81 | fullfill() 82 | }.catch { error in 83 | print(error.localizedDescription) 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/Talk Event/TalkEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TalkEventListeners.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/08/04. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import JSONSchema 12 | import SpriteKit 13 | import PromiseKit 14 | 15 | class TalkEventListener: EventListenerImplement { 16 | fileprivate var index: Int? = nil 17 | fileprivate var talkContentsMaxNum: Int? = nil 18 | 19 | required convenience init(params: JSON?, chainListeners listeners: ListenerChain?) throws { 20 | do { 21 | try self.init(params: params, chainListeners: listeners, index: 1) 22 | } catch { 23 | throw error 24 | } 25 | } 26 | 27 | init(params: JSON?, chainListeners listeners: ListenerChain?, index: Int) throws { 28 | try! super.init(params: params, chainListeners: listeners) 29 | 30 | // TODO: 31 | // The truth is validation as follows should be executed by JSONSchema, 32 | // but JSONSchema doesn't work well with nested JSON. 33 | // This is because that determine whether it's object or not by whether it's castable to NSDicationary 34 | // let schema = Schema([ 35 | // "type": "object", 36 | // "minProperties": 1, 37 | // ]) 38 | 39 | if params == nil { throw EventListenerError.illegalParamFormat(["Parameter is nil"]) } 40 | 41 | let maxIndex = params?.arrayObject?.count 42 | if maxIndex == nil { throw EventListenerError.illegalParamFormat(["No properties in json parameter"]) } 43 | 44 | self.index = index 45 | self.triggerType = .touch 46 | // TODO: The value of following variable is determined by the number of property. 47 | // It might have to be defined specifically. 48 | // a number of conversation times 49 | self.talkContentsMaxNum = (params?.arrayObject?.count)! 50 | self.rollback = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 51 | sender?.actionButton.isHidden = true 52 | sender?.textBox.hide() 53 | return Promise { fullfill, reject in fullfill() } 54 | } 55 | self.invoke = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 56 | do { 57 | let moveConversation = try TalkEventListener.generateMoveConversationMethod(self.index!, params: self.params!) 58 | try moveConversation(sender, args).catch { error in 59 | // TODO 60 | } 61 | 62 | // Determine the Event Listener which executed in next 63 | // depending on whether conversation is continued or not. 64 | let nextEventListener: EventListener 65 | if index < self.talkContentsMaxNum! - 1 { 66 | nextEventListener = try TalkEventListener(params: self.params, chainListeners: self.listeners, index: self.index!+1) 67 | } else { 68 | nextEventListener = try FinishTalkEventListener(params: self.params, chainListeners: self.listeners) 69 | } 70 | nextEventListener.eventObjectId = self.eventObjectId 71 | nextEventListener.isBehavior = self.isBehavior 72 | 73 | self.delegate?.invoke(nextEventListener, invoker: self) 74 | } catch { 75 | throw error 76 | } 77 | 78 | return Promise { fullfill, reject in fullfill() } 79 | } 80 | } 81 | 82 | static func generateMoveConversationMethod(_ index: Int, params: JSON) throws -> EventMethod { 83 | let schema = Schema([ 84 | "type": "object", 85 | "properties": [ 86 | "talker": ["type": "string"], 87 | "talk_body": ["type": "string"], 88 | "talk_side": [ 89 | "type": "string", 90 | "enum": ["L", "R"] 91 | ], 92 | ], 93 | "required": ["talker", "talk_body", "talk_side"], 94 | ]) 95 | let result = schema.validate(params[index].rawValue) 96 | if result.valid == false { 97 | throw EventListenerError.illegalParamFormat(result.errors!) 98 | } 99 | if TALKER_IMAGE.index(forKey: params[index]["talker"].string!) == nil { 100 | throw EventListenerError.invalidParam("Talker image name specified at param `talker` is not declared in configuration file") 101 | } 102 | 103 | let talker = params[index]["talker"].string! 104 | let talkBody = params[index]["talk_body"].string! 105 | let talkSideString = params[index]["talk_side"].string! 106 | let talkSide = talkSideString == "L" ? Dialog.TALK_SIDE.left : Dialog.TALK_SIDE.right 107 | let talkerImageName = TALKER_IMAGE[talker] 108 | 109 | return { sender, args in 110 | sender!.textBox.drawText(talkerImageName, body: talkBody, side: talkSide) 111 | return Promise { fullfill, reject in fullfill() } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/WaitEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WaitEventListener.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2017/01/25. 6 | // Copyright © 2017年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SpriteKit 12 | import SwiftyJSON 13 | import JSONSchema 14 | import PromiseKit 15 | 16 | class WaitEventListener: EventListenerImplement { 17 | required init(params: JSON?, chainListeners listeners: ListenerChain?) throws { 18 | try! super.init(params: params, chainListeners: listeners) 19 | 20 | let schema = Schema([ 21 | "type": "object", 22 | "properties": [ 23 | "time": ["type": "string"] 24 | ], 25 | "required": ["time"], 26 | ] 27 | ) 28 | let result = schema.validate(params?.rawValue ?? []) 29 | if result.valid == false { 30 | throw EventListenerError.illegalParamFormat(result.errors!) 31 | } 32 | // TODO: Validation as following must be executed as a part of validation by JSONSchema 33 | if (Int(params!["time"].string!) == nil) { 34 | throw EventListenerError.illegalParamFormat(["The parameter 'time' couldn't convert to integer"]) 35 | } 36 | 37 | self.triggerType = .immediate 38 | self.invoke = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 39 | self.isExecuting = true 40 | 41 | let map = sender!.map! 42 | let time = Int((self.params?["time"].string!)!)! 43 | 44 | return Promise { fullfill, reject in 45 | firstly { 46 | self.generatePromiseClojureForWaiting(map: map, time: time) 47 | }.then { _ -> Void in 48 | do { 49 | let nextEventListener = try InvokeNextEventListener(params: self.params, chainListeners: self.listeners) 50 | nextEventListener.eventObjectId = self.eventObjectId 51 | nextEventListener.isBehavior = self.isBehavior 52 | self.delegate?.invoke(nextEventListener, invoker: self) 53 | } catch { 54 | throw error 55 | } 56 | }.then { 57 | fullfill() 58 | }.catch { error in 59 | print(error.localizedDescription) 60 | } 61 | } 62 | } 63 | } 64 | 65 | // TODO: Currently, for delaying animated, run delay action for Map SKSpriteNode. 66 | // This might be problem if we wanted to animate map. 67 | fileprivate func generatePromiseClojureForWaiting(map: Map, time: Int) -> Promise { 68 | return Promise { 69 | fullfill, reject in 70 | map.wait(time, callback: { () in fullfill() }) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/WalkEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TouchEventListener.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/07/29. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SpriteKit 12 | import SwiftyJSON 13 | import JSONSchema 14 | import PromiseKit 15 | 16 | /// プレイヤー移動のリスナー 17 | class WalkEventListener: EventListenerImplement { 18 | required init(params: JSON?, chainListeners listeners: ListenerChain?) { 19 | try! super.init(params: params, chainListeners: listeners) 20 | 21 | self.triggerType = .touch 22 | self.invoke = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 23 | let schema = Schema([ 24 | "type": "object", 25 | "properties": [ 26 | "touchedPoint": ["type": "string"], 27 | ], 28 | "required": ["touchedPoint"], 29 | ]) 30 | let result = schema.validate(args?.rawValue ?? []) 31 | if result.valid == false { 32 | throw EventListenerError.illegalParamFormat(result.errors!) 33 | } 34 | // TODO: The following method end up throw exception even in the case the 'touchedPoint' value is (0,0). 35 | if CGPointFromString((args?["touchedPoint"].string)!) == CGPoint.init(x: 0, y: 0) { 36 | throw EventListenerError.illegalParamFormat(["The parameter 'touchedPoint' isn't castable to CGPoint."]) 37 | } 38 | 39 | let map = sender!.map! 40 | let sheet = map.sheet! 41 | 42 | let touchedPoint = CGPointFromString((args?["touchedPoint"].string)!) 43 | let player = map.getObjectByName(objectNameTable.PLAYER_NAME)! 44 | let departure = TileCoordinate.getTileCoordinateFromSheetCoordinate(player.position) 45 | let destination = TileCoordinate.getTileCoordinateFromScreenCoordinate( 46 | sheet.getSheetPosition(), 47 | screenCoordinate: touchedPoint 48 | ) 49 | 50 | // Route search 51 | let aStar = AStar(map: map) 52 | aStar.initialize(departure, destination: destination) 53 | let path = aStar.main() 54 | if path == nil { 55 | let nextEventListener = WalkEventListener.init(params: nil, chainListeners: nil) 56 | nextEventListener.eventObjectId = self.eventObjectId 57 | nextEventListener.isBehavior = self.isBehavior 58 | self.delegate?.invoke(nextEventListener, invoker: self) 59 | return Promise { fullfill, reject in fullfill() } 60 | } 61 | 62 | // Generate event listener chain for walking animation 63 | var chain: ListenerChain = [] 64 | for step: TileCoordinate in path! { 65 | chain = chain + self.generateOneStepWalkListener(step) 66 | } 67 | 68 | let nextListener = chain.first!.listener 69 | let nextListenerChain: ListenerChain? = chain.count == 1 ? nil : Array(chain.dropFirst()) 70 | do { 71 | let nextListenerInstance = try nextListener.init(params: chain.first!.params, chainListeners: nextListenerChain) 72 | nextListenerInstance.eventObjectId = self.eventObjectId 73 | nextListenerInstance.isBehavior = self.isBehavior 74 | self.delegate?.invoke(nextListenerInstance, invoker: self) 75 | } catch { 76 | throw error 77 | } 78 | 79 | return Promise { fullfill, reject in fullfill() } 80 | } 81 | } 82 | 83 | fileprivate func generateOneStepWalkListener(_ destination: TileCoordinate) -> ListenerChain { 84 | return [ (listener: WalkOneStepEventListener.self, params: JSON(["destination": destination.description])) ] 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventListener/WalkOneStepEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WalkOneStepEventListener.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2017/01/26. 6 | // Copyright © 2017年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SpriteKit 12 | import SwiftyJSON 13 | import JSONSchema 14 | import PromiseKit 15 | 16 | class WalkOneStepEventListener: EventListenerImplement { 17 | required init(params: JSON?, chainListeners listeners: ListenerChain?) throws { 18 | try! super.init(params: params, chainListeners: listeners) 19 | 20 | let schema = Schema([ 21 | "type": "object", 22 | "properties": [ 23 | "destination": ["type": "string"], 24 | ], 25 | "required": ["destination"], 26 | ]) 27 | let result = schema.validate(params?.rawValue ?? []) 28 | if result.valid == false { 29 | throw EventListenerError.illegalParamFormat(result.errors!) 30 | } 31 | 32 | self.triggerType = .immediate 33 | self.invoke = { (sender: GameSceneProtocol?, args: JSON?) -> Promise in 34 | self.isExecuting = true 35 | 36 | let map = sender!.map! 37 | let sheet = map.sheet! 38 | 39 | let player = map.getObjectByName(objectNameTable.PLAYER_NAME)! 40 | let destination = TileCoordinate.parse(from: (self.params?["destination"].string)!) 41 | 42 | // Generate SKAction for moving 43 | let action = player.getActionTo( 44 | player.position, 45 | destination: TileCoordinate.getSheetCoordinateFromTileCoordinate(destination), 46 | preCallback: { 47 | map.setCollisionOn(coordinate: destination) 48 | }, 49 | postCallback: { 50 | map.removeCollisionOn(coordinate: destination) 51 | map.updateObjectPlacement(player, departure: player.coordinate, destination: destination) 52 | } 53 | ) 54 | 55 | let screenAction = sheet.getActionTo( 56 | player.position, 57 | destination: TileCoordinate.getSheetCoordinateFromTileCoordinate(destination), 58 | speed: player.speed 59 | ) 60 | 61 | // If player can't reach destination tile because of collision, stop 62 | if !map.canPass(destination) { 63 | self.delegate?.invoke(WalkEventListener.init(params: nil, chainListeners: nil), invoker: self) 64 | return Promise { fullfill, reject in fullfill() } 65 | } 66 | 67 | return Promise { fullfill, reject in 68 | firstly { 69 | sender!.movePlayer( 70 | action, 71 | departure: player.coordinate, 72 | destination: destination, 73 | screenAction: screenAction, 74 | invoker: self 75 | ) 76 | }.then { _ -> Void in 77 | // If reached at destination, stop walking and set WalkEvetListener as touch event again 78 | if self.listeners == nil || self.listeners?.count == 0 79 | || map.getEventsOn(destination).isEmpty == false { 80 | let nextEventListener = WalkEventListener.init(params: nil, chainListeners: nil) 81 | nextEventListener.eventObjectId = self.eventObjectId 82 | nextEventListener.isBehavior = self.isBehavior 83 | self.delegate?.invoke(nextEventListener, invoker: self) 84 | return 85 | } 86 | 87 | // If player don't reach at destination, invoke next step animation listener 88 | let nextListener = self.listeners!.first!.listener 89 | let nextListenerChain: ListenerChain? = self.listeners!.count == 1 ? nil : Array(self.listeners!.dropFirst()) 90 | do { 91 | let nextListenerInstance = try nextListener.init(params: self.listeners!.first!.params, chainListeners: nextListenerChain) 92 | nextListenerInstance.isBehavior = self.isBehavior 93 | self.delegate?.invoke(nextListenerInstance, invoker: self) 94 | } catch { 95 | throw error 96 | } 97 | }.then { 98 | fullfill() 99 | }.catch { error in 100 | print(error.localizedDescription) 101 | } 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Event/EventManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventManager.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/08/02. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | protocol NotifiableFromDispacher { 13 | func invoke(_ listener: EventListener, invoker: EventListener) 14 | func removed(_ listenerId: UInt64, sender: EventDispatcher) 15 | } 16 | 17 | protocol UnavailabledCyclicEventIdsContainable { 18 | var unavailabledCyclicEventIds: [UInt64] { get } 19 | } 20 | 21 | enum EventManagerError: Error { 22 | case FailedToTrigger(String) 23 | } 24 | 25 | class EventManager: NotifiableFromDispacher, UnavailabledCyclicEventIdsContainable { 26 | fileprivate var touchEventDispacher: EventDispatcher 27 | fileprivate var actionButtonEventDispacher: EventDispatcher 28 | fileprivate var cyclicEventDispacher: EventDispatcher 29 | 30 | fileprivate(set) var isBlockingBehavior: Bool = true 31 | fileprivate(set) var isBlockingWalking: Bool = true 32 | fileprivate(set) var isBlockingTrigger: Bool = false 33 | internal var unavailabledCyclicEventIds: [UInt64] = [] 34 | 35 | init() { 36 | self.touchEventDispacher = EventDispatcher(.touch) 37 | self.actionButtonEventDispacher = EventDispatcher(.button) 38 | self.cyclicEventDispacher = EventDispatcher(.immediate) 39 | 40 | self.touchEventDispacher.delegate = self 41 | self.actionButtonEventDispacher.delegate = self 42 | self.cyclicEventDispacher.delegate = self 43 | } 44 | 45 | @discardableResult 46 | func add(_ listener: EventListener) -> Bool { 47 | let listeners = self.getAllListeners() 48 | if listener.eventObjectId != nil { 49 | for listener_ in listeners { 50 | if listener_.eventObjectId == listener.eventObjectId 51 | && listener_.isBehavior == listener.isBehavior { 52 | return false 53 | } 54 | } 55 | } 56 | let dispacher = self.getDispacherOf(listener.triggerType) 57 | if dispacher.add(listener) == false { 58 | return false 59 | } else { 60 | return true 61 | } 62 | } 63 | 64 | @discardableResult 65 | func remove(_ id: MapObjectId, sender: GameSceneProtocol? = nil) -> Bool { 66 | let listeners = self.getAllListeners() 67 | var targetListener: EventListener? = nil 68 | var targetDispacher: EventDispatcher? = nil 69 | 70 | for listener in listeners { 71 | if listener.eventObjectId == id 72 | && listener.isBehavior == false { 73 | targetDispacher = self.getDispacherOf(listener.triggerType) 74 | targetListener = listener 75 | break 76 | } 77 | } 78 | 79 | if let l = targetListener, 80 | let d = targetDispacher { 81 | return d.remove(l, sender: sender) 82 | } 83 | 84 | return false 85 | } 86 | 87 | func trigger(_ type: TriggerType, sender: GameSceneProtocol!, args: JSON!) throws { 88 | if isBlockingTrigger { return } 89 | 90 | let dispacher = self.getDispacherOf(type) 91 | do { 92 | try dispacher.trigger(sender, args: args) 93 | } catch EventDispacherError.FiledToInvokeListener(let string) { 94 | throw EventManagerError.FailedToTrigger(string) 95 | } 96 | } 97 | 98 | // MARK: - Unavailable, Block/Unblock methods 99 | 100 | // Permanently block event listeners which existed at the time of called this function 101 | // TODO: Block touch event during executing this function 102 | func unavailableAllListeners() { 103 | self.isBlockingTrigger = true 104 | 105 | // Make target listeners already added to unavailable 106 | let listeners = self.getAllListeners() 107 | for listener in listeners { 108 | if listener.triggerType == .touch { 109 | if self.touchEventDispacher.remove(listener) == false { 110 | print("Failed to remove listener") 111 | } 112 | continue 113 | } 114 | if listener.triggerType == .button { 115 | if self.actionButtonEventDispacher.remove(listener) == false { 116 | print("Failed to remove listener") 117 | } 118 | continue 119 | } 120 | if listener.triggerType == .immediate { 121 | self.unavailabledCyclicEventIds.append(listener.id) 122 | continue 123 | } 124 | } 125 | 126 | // The controller decide by these flags that whether must add listeners or not. 127 | // So if remove listeners of walking or behavior, these flags should be true. 128 | self.isBlockingWalking = true 129 | self.isBlockingBehavior = true 130 | 131 | self.isBlockingTrigger = false 132 | } 133 | 134 | func blockBehavior() { 135 | self.isBlockingTrigger = true 136 | 137 | // Make target listeners already added to unavailable 138 | let listeners = self.cyclicEventDispacher.getAllListeners() 139 | for l in listeners { 140 | if l.isBehavior { 141 | self.unavailabledCyclicEventIds.append(l.id) 142 | } 143 | } 144 | 145 | // Prevent to invoke other listener for target listener 146 | self.isBlockingBehavior = true 147 | 148 | self.isBlockingTrigger = false 149 | } 150 | 151 | func unblockBehavior() { 152 | self.isBlockingBehavior = false 153 | } 154 | 155 | func blockWalking() { 156 | self.isBlockingTrigger = true 157 | 158 | // Make target listeners already added to unavailable 159 | let tListeners = self.touchEventDispacher.getAllListeners() 160 | for l in tListeners { 161 | if (l as? WalkEventListener) != nil { 162 | self.touchEventDispacher.remove(l) 163 | } 164 | } 165 | let cListeners = self.cyclicEventDispacher.getAllListeners() 166 | for l in cListeners { 167 | if (l as? WalkOneStepEventListener) != nil { 168 | self.unavailabledCyclicEventIds.append(l.id) 169 | } 170 | } 171 | 172 | // Prevent to invoke other listener for target listener 173 | self.isBlockingWalking = true 174 | 175 | self.isBlockingTrigger = false 176 | } 177 | 178 | func unblockWalking() { 179 | self.isBlockingWalking = false 180 | } 181 | 182 | // MARK: - 183 | 184 | func existsListeners(_ type: TriggerType) -> Bool { 185 | let dispathcer = self.getDispacherOf(type) 186 | let listeners = dispathcer.getAllListeners() 187 | return !listeners.isEmpty 188 | } 189 | 190 | func shouldActivateButton() -> Bool { 191 | let listeners = self.getDispacherOf(.immediate).getAllListeners() 192 | for listener in listeners { 193 | if let _ = listener as? ActivateButtonListener { 194 | return true 195 | } 196 | } 197 | return false 198 | } 199 | 200 | // MARK: - Private methods 201 | 202 | fileprivate func getAllListeners() -> [EventListener] { 203 | var listenersDic: Dictionary = [:] 204 | var listeners: [EventListener] = [] 205 | listeners += self.touchEventDispacher.getAllListeners() 206 | listeners += self.actionButtonEventDispacher.getAllListeners() 207 | listeners += self.cyclicEventDispacher.getAllListeners() 208 | 209 | for listener in listeners { 210 | listenersDic[listener.id] = listener 211 | } 212 | 213 | for (id, _) in listenersDic { 214 | if self.unavailabledCyclicEventIds.contains(id) { 215 | listenersDic.removeValue(forKey: id) 216 | } 217 | } 218 | 219 | listeners = [] 220 | for listener in listenersDic.values { 221 | listeners.append(listener) 222 | } 223 | 224 | return listeners 225 | } 226 | 227 | fileprivate func getDispacherOf(_ type: TriggerType) -> EventDispatcher { 228 | switch type { 229 | case .touch: 230 | return self.touchEventDispacher 231 | case .button: 232 | return self.actionButtonEventDispacher 233 | case .immediate: 234 | return self.cyclicEventDispacher 235 | } 236 | } 237 | 238 | // MARK: - NotifiableFromDispacher 239 | 240 | func invoke(_ listener: EventListener, invoker: EventListener) { 241 | let nextListenersDispacher = self.getDispacherOf(listener.triggerType) 242 | 243 | for id in self.unavailabledCyclicEventIds { 244 | if id == invoker.id && invoker.triggerType == .immediate { 245 | return 246 | } 247 | } 248 | 249 | if isBlockingWalking && (listener as? WalkOneStepEventListener != nil) { 250 | return 251 | } 252 | 253 | if isBlockingBehavior && listener.isBehavior { 254 | return 255 | } 256 | 257 | if !nextListenersDispacher.add(listener) { 258 | print("Failed to add listener" ) 259 | } 260 | } 261 | 262 | func removed(_ listenerId: UInt64, sender: EventDispatcher) { 263 | if sender.triggerType == .immediate { 264 | if self.unavailabledCyclicEventIds.contains(listenerId) { 265 | self.unavailabledCyclicEventIds = self.unavailabledCyclicEventIds.filter { $0 != listenerId } 266 | } 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Map/Map.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Map.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2016/02/22. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SpriteKit 11 | 12 | open class Map { 13 | /// タイルシート 14 | fileprivate(set) var sheet: TileSheet? = nil 15 | 16 | /// コンストラクタ 17 | /// 18 | /// - parameter mapName: マップである JSON ファイルの名前 19 | /// - parameter frameWidth: フレームの幅 20 | /// - parameter frameHeight: フレームの高さ 21 | /// 22 | /// - returns: 23 | init?(mapName: String, 24 | frameWidth: CGFloat, 25 | frameHeight: CGFloat) 26 | { 27 | let parser: TiledMapJsonParser 28 | do { 29 | parser = try TiledMapJsonParser(fileName: mapName) 30 | } catch ParseError.illegalJsonFormat { 31 | print("Invalid JSON format in \(mapName)") 32 | return nil 33 | } catch ParseError.jsonFileNotFound { 34 | print("JSON file \(mapName) is not found") 35 | return nil 36 | } catch { 37 | return nil 38 | } 39 | 40 | let tiles: Dictionary 41 | var objects: Dictionary 42 | let tileEvents: Dictionary 43 | let objectEvents: Dictionary 44 | do { 45 | let cols, rows: Int 46 | (cols, rows) = try parser.getLayerSize() 47 | let tileProperties = try parser.getTileProperties() 48 | let tileSets = try parser.getTileSets() 49 | let collisionLayer = try parser.getInfoFromLayer(cols, layerTileRows: rows, kind: .collision) 50 | let tileLayer = try parser.getInfoFromLayer(cols, layerTileRows: rows, kind: .tile) 51 | let objectLayer = try parser.getInfoFromLayer(cols, layerTileRows: rows, kind: .object) 52 | (tiles, tileEvents) = try Tile.createTiles( 53 | rows, 54 | cols: cols, 55 | properties: tileProperties, 56 | tileSets: tileSets, 57 | collisionPlacement: collisionLayer, 58 | tilePlacement: tileLayer) 59 | (objects, objectEvents) = try Object.createObjects( 60 | tiles, 61 | properties: tileProperties, 62 | tileSets: tileSets, 63 | objectPlacement: objectLayer) 64 | } catch ParseError.invalidValueError(let string) { 65 | print(string) 66 | return nil 67 | } catch ParseError.swiftyJsonError(let errors) { 68 | for error in errors { print(error!) } 69 | return nil 70 | } catch MapObjectError.failedToGenerate(let string) { 71 | print(string) 72 | return nil 73 | } catch { 74 | return nil 75 | } 76 | 77 | // Merge events of tile and events of object 78 | var events: Dictionary = [:] 79 | for (coordinate, event) in tileEvents { 80 | if events[coordinate] != nil { 81 | events[coordinate]!.append(event) 82 | } else { 83 | events[coordinate] = [event] 84 | } 85 | } 86 | for (coordinate, event) in objectEvents { 87 | if events[coordinate] != nil { 88 | events[coordinate]! = events[coordinate]! + event 89 | } else { 90 | events[coordinate] = event 91 | } 92 | } 93 | 94 | let sheet = TileSheet(parser: parser, 95 | frameWidth: frameWidth, 96 | frameHeight: frameHeight, 97 | tiles: tiles, 98 | objects: objects, 99 | events: events) 100 | self.sheet = sheet! 101 | } 102 | 103 | func wait(_ time: Int, callback: @escaping () -> Void) { 104 | self.sheet?.runAction([SKAction.wait(forDuration: TimeInterval(time))], callback: callback) 105 | } 106 | 107 | func addSheetTo(_ scene: SKScene) { 108 | self.sheet!.addTo(scene) 109 | } 110 | 111 | func getObjectBehavior(_ id: MapObjectId) -> ListenerChain? { 112 | return self.sheet?.getObjectBehavior(id) 113 | } 114 | 115 | func getObjectByName(_ name: String) -> Object? { 116 | return self.sheet?.getObjectByName(name) 117 | } 118 | 119 | func getObjectCoordinateByName(_ name: String) -> TileCoordinate? { 120 | return self.sheet?.getObjectCoordinateByName(name) 121 | } 122 | 123 | func removeEventsOfObject(_ objectId: MapObjectId) { 124 | self.sheet?.removeEventsOfObject(objectId) 125 | } 126 | 127 | func getAllObjects() -> [Object] { 128 | return (self.sheet?.getAllObjects())! 129 | } 130 | 131 | func setObject(_ object: Object) { 132 | let coordinate = TileCoordinate.getTileCoordinateFromSheetCoordinate(object.position) 133 | self.sheet?.setObject(object: object, coordinate: coordinate) 134 | } 135 | 136 | func setEventsOf(_ objectId: MapObjectId, coordinate: TileCoordinate) { 137 | self.sheet?.setEventsOf(objectId, coordinate: coordinate) 138 | } 139 | 140 | func getMapObjectsOn(_ coordinate: TileCoordinate) -> [MapObject]? { 141 | return self.sheet?.getMapObjectsOn(coordinate) 142 | } 143 | 144 | func getEventsOn(_ coordinate: TileCoordinate) -> [EventListener] { 145 | return (self.sheet?.getEventsOn(coordinate))! 146 | } 147 | 148 | func setCollisionOn(coordinate: TileCoordinate) { 149 | self.sheet?.getTileOn(coordinate)!.setCollision() 150 | } 151 | 152 | func removeCollisionOn(coordinate: TileCoordinate) { 153 | self.sheet?.getTileOn(coordinate)!.removeCollision() 154 | } 155 | 156 | func canPass(_ coordinate: TileCoordinate) -> Bool { 157 | if let mapObjects = self.sheet?.getMapObjectsOn(coordinate) { 158 | for mapObject in mapObjects { 159 | if mapObject.hasCollision { return false } 160 | } 161 | } 162 | return true 163 | } 164 | 165 | func updateObjectPlacement(_ object: Object, departure: TileCoordinate, destination: TileCoordinate) { 166 | self.sheet?.replaceObject(object.id, departure: departure, destination: destination) 167 | } 168 | 169 | /// オブジェクトのZ方向の位置を更新する 170 | func updateObjectsZPosition() { 171 | var objects: [(Object, CGFloat)] = [] 172 | 173 | for object in (self.sheet?.getAllObjects())! { 174 | objects.append((object, object.position.y)) 175 | } 176 | 177 | // Y座標に基づいてオブジェクトを並べ替え,zPosition を更新する 178 | objects.sort { $0.1 > $1.1 } 179 | let base = zPositionTable.BASE_OBJECT_POSITION 180 | var incremental: CGFloat = 0.0 181 | for (obj, _) in objects { 182 | obj.setZPosition(base + incremental) 183 | incremental += 1 184 | } 185 | } 186 | 187 | func getEventsOnPlayerPosition() -> [EventListener]? { 188 | let player = self.getObjectByName(objectNameTable.PLAYER_NAME) 189 | let events = self.getEventsOn((player?.coordinate)!) 190 | if events.isEmpty { 191 | return nil 192 | } else { 193 | return events 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Map/TileSheet/MapObject/EventObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventObject.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2017/01/26. 6 | // Copyright © 2017年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SpriteKit 12 | 13 | open class EventObject: MapObject { 14 | fileprivate(set) var eventListener: EventListener 15 | fileprivate(set) var relativeCoordinateFromParent: TileCoordinate 16 | fileprivate(set) var relatedEventListenerId: Int? 17 | 18 | // MARK: - MapObject 19 | 20 | fileprivate(set) var id: MapObjectId 21 | fileprivate(set) var hasCollision: Bool 22 | fileprivate var parent_: MapObjectId? 23 | var parent: MapObjectId? { 24 | get { 25 | return self.parent_ 26 | } 27 | set { 28 | self.parent_ = newValue 29 | } 30 | } 31 | fileprivate var children_: [MapObjectId] = [] 32 | var children: [MapObjectId] { 33 | get { 34 | return self.children_ 35 | } 36 | set { 37 | self.children_ = newValue 38 | } 39 | } 40 | func setCollision() { 41 | self.hasCollision = true 42 | } 43 | static var nextId = 0 44 | static func generateId() -> MapObjectId { 45 | nextId += 1 46 | return nextId 47 | } 48 | 49 | // MARK: - 50 | 51 | init(parentId: MapObjectId, relativeCoordinate: TileCoordinate, event: EventListener) { 52 | self.id = EventObject.generateId() 53 | self.parent_ = parentId 54 | self.relativeCoordinateFromParent = relativeCoordinate 55 | event.eventObjectId = self.id 56 | self.eventListener = event 57 | self.hasCollision = false 58 | } 59 | 60 | func registerListenerId(listenerId: Int) { 61 | self.relatedEventListenerId = listenerId 62 | } 63 | 64 | func removeListenerId() { 65 | self.relatedEventListenerId = nil 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Map/TileSheet/MapObject/MapObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapObject.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2016/02/15. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum MapObjectError: Error { 12 | case failedToGenerate(String) 13 | } 14 | 15 | typealias MapObjectId = Int 16 | protocol MapObject { 17 | // TODO: MapObjectId will confused because it could contain different class's id which is inherit MapObject 18 | /// Map object could have parent-child relationship. 19 | /// There are some usage for these... 20 | /// - Sometimes you want to tying events to the map object. 21 | /// This could realize by adding map objects having event to target map object as children, 22 | /// and manage the child map object's as "event object" 23 | /// - MapObject should have only one tile coordinate. 24 | /// But sometimes you want to add large map object (i.e. the map object acrossing some tiles 25 | /// In above situation, parent-child relationship of map object could use for grouping for map objects. 26 | 27 | var parent: MapObjectId? { get set } 28 | 29 | var children: [MapObjectId] { get set } 30 | 31 | /// For collision detection 32 | 33 | var hasCollision: Bool { get } 34 | 35 | func setCollision() 36 | 37 | /// Each map object has id. 38 | /// This id should be used for identification of objects 39 | /// which inherit same *class*, not same *protocol*. 40 | 41 | var id: MapObjectId { get } 42 | 43 | static var nextId: MapObjectId { get } 44 | 45 | static func generateId() -> MapObjectId 46 | } 47 | 48 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Map/TileSheet/MapObject/Tile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tile.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2015/08/03. 6 | // Copyright © 2015年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SpriteKit 12 | 13 | typealias TileID = Int 14 | typealias TileSetID = Int 15 | typealias TileProperty = Dictionary 16 | 17 | /// マップ上に敷かれる各タイルに対応した SKSpriteNode のラッパークラス 18 | open class Tile: MapObject { 19 | static var TILE_SIZE: CGFloat = 32.0 20 | fileprivate let tileId: Int 21 | fileprivate let node: SKSpriteNode 22 | fileprivate(set) var coordinate: TileCoordinate 23 | fileprivate(set) var property: TileProperty 24 | 25 | // MARK: - MapObject 26 | 27 | fileprivate(set) var id: MapObjectId 28 | fileprivate(set) var hasCollision: Bool 29 | fileprivate var parent_: MapObjectId? 30 | var parent: MapObjectId? { 31 | get { 32 | return self.parent_ 33 | } 34 | set { 35 | self.parent_ = newValue 36 | } 37 | } 38 | fileprivate var children_: [MapObjectId] = [] 39 | var children: [MapObjectId] { 40 | get { 41 | return self.children_ 42 | } 43 | set { 44 | self.children_ = newValue 45 | } 46 | } 47 | func setCollision() { 48 | self.hasCollision = true 49 | } 50 | static var nextId = 0 51 | static func generateId() -> MapObjectId { 52 | nextId += 1 53 | return nextId 54 | } 55 | 56 | // MARK: - 57 | 58 | /// コンストラクタ 59 | /// 60 | /// - parameter coordinate: タイルの座標 61 | /// - parameter event: タイルに配置するイベント 62 | /// 63 | /// - returns: なし 64 | init(id: TileID, coordinate: TileCoordinate, property: TileProperty) { 65 | self.id = Tile.generateId() 66 | let x = coordinate.x 67 | let y = coordinate.y 68 | self.tileId = id 69 | self.node = SKSpriteNode() 70 | self.node.size = CGSize(width: CGFloat(Tile.TILE_SIZE), 71 | height: CGFloat(Tile.TILE_SIZE)) 72 | self.node.position = CGPoint(x: CGFloat(x - 1) * Tile.TILE_SIZE, 73 | y: CGFloat(y - 1) * Tile.TILE_SIZE) 74 | self.node.anchorPoint = CGPoint(x: 0.0, y: 0.0) 75 | self.node.zPosition = zPositionTable.TILE 76 | self.coordinate = TileCoordinate(x: x, y: y) 77 | self.hasCollision = false 78 | self.property = property 79 | } 80 | 81 | /// タイルにテクスチャ画像を付加する 82 | /// 83 | /// - parameter imageName: 付加するテクスチャ画像名 84 | func setImageWithName(_ imageName: String) { 85 | node.texture = SKTexture(imageNamed: imageName) 86 | } 87 | 88 | /// タイルにテクスチャ画像を付加する 89 | /// 90 | /// - parameter image: 付加するテクスチャ画像 91 | func setImageWithUIImage(_ image: UIImage) { 92 | node.texture = SKTexture(image: image) 93 | } 94 | 95 | /// タイルのノードに子ノードを追加する 96 | /// 97 | /// - parameter node: 追加する子ノード 98 | func addTo(_ node: SKSpriteNode) { 99 | node.addChild(self.node) 100 | } 101 | 102 | func removeCollision() { 103 | self.hasCollision = false 104 | } 105 | 106 | // MARK: - class method 107 | 108 | /// タイル群を生成する 109 | /// 110 | /// - parameter rows: タイルを敷き詰める列数 111 | /// - parameter cols: タイルを敷き詰める行数 112 | /// - parameter properties: タイル及びオブジェクトのプロパティ 113 | /// - parameter tileSets: タイルセットの情報 114 | /// - parameter collisionPlacement: マップにおける当たり判定の配置 115 | /// - parameter tilePlacement: マップにおけるタイルの配置 116 | /// 117 | /// - throws: 118 | /// 119 | /// - returns: 生成したタイル群 120 | class func createTiles( 121 | _ rows: Int, 122 | cols: Int, 123 | properties: Dictionary, 124 | tileSets: Dictionary, 125 | collisionPlacement: Dictionary, 126 | tilePlacement: Dictionary 127 | ) throws -> 128 | (tiles: Dictionary, events: Dictionary) 129 | { 130 | var tiles: Dictionary = [:] 131 | var eventObjects: Dictionary = [:] 132 | for (coordinate, tileID) in tilePlacement { 133 | let tileProperty = properties[tileID] 134 | if tileProperty == nil { 135 | throw MapObjectError.failedToGenerate("tileID \(tileID.description)'s property is not defined in properties(\(properties.description))") 136 | } 137 | 138 | // タイルを作成する 139 | let tile = Tile( 140 | id: tileID, 141 | coordinate: coordinate, 142 | property: tileProperty! 143 | ) 144 | 145 | // 当たり判定を付加する 146 | let hasCollision = collisionPlacement[coordinate] 147 | if hasCollision == nil { 148 | throw MapObjectError.failedToGenerate("Coordinate(\(coordinate.description)) specified in tilePlacement is not defined at collisionPlacement(\(collisionPlacement.description))") 149 | } 150 | if hasCollision != 0 { 151 | tile.setCollision() 152 | } 153 | 154 | // 画像を付与する 155 | let tileSetID = Int(tile.property["tileSetID"]!) 156 | if tileSetID == nil { 157 | throw MapObjectError.failedToGenerate("tileSetID is not defined in tile \(tile)'s property(\(tile.property.description))") 158 | } 159 | 160 | let tileSet = tileSets[tileSetID!] 161 | if tileSet == nil { 162 | throw MapObjectError.failedToGenerate("tileSet(ID = \(tileSetID?.description)) is not defined in tileSets(\(tileSets.description))") 163 | } 164 | 165 | let tileImage: UIImage? 166 | do { 167 | tileImage = try tileSet!.cropTileImage(tileID) 168 | } catch { 169 | throw MapObjectError.failedToGenerate("Failed to crop image of object which tileID is \(tileID)") 170 | } 171 | tile.setImageWithUIImage(tileImage!) 172 | 173 | if let action = tile.property["event"] { 174 | let listeners: Dictionary 175 | do { 176 | let properties = try EventPropertyParser.parse(from: action) 177 | listeners = try ListenerGenerator.generate(properties: properties) 178 | } catch EventParserError.invalidProperty(let string) { 179 | throw MapObjectError.failedToGenerate("Failed to generate event listener: " + string) 180 | } catch ListenerGeneratorError.failed(let string) { 181 | throw MapObjectError.failedToGenerate("Failed to generate event listener: " + string) 182 | } 183 | 184 | if listeners.count == 0 { 185 | break 186 | } 187 | if listeners.count > 1 { 188 | throw MapObjectError.failedToGenerate("Tile event should has only one relative coordinate") 189 | } 190 | if listeners.count == 1 && listeners.first?.key != TileCoordinate(x:0,y:0) { 191 | throw MapObjectError.failedToGenerate("Tile event should has (0,0) relative coordinate") 192 | } 193 | 194 | let listener = listeners.first!.value 195 | 196 | // Create event object for this tile 197 | let eventObject = EventObject(parentId: tile.id, relativeCoordinate: coordinate, event: listener) 198 | tile.children.append(eventObject.id) 199 | eventObjects[tile.coordinate] = eventObject 200 | } 201 | 202 | tiles[coordinate] = tile 203 | } 204 | return (tiles: tiles, events: eventObjects) 205 | } 206 | 207 | // MARK: - 208 | } 209 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Map/TileSheet/MapObject/util/BehaviorPropertyParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BehaviorPropertyParser.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/12/25. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SpriteKit 11 | import SwiftyJSON 12 | 13 | class BehaviorPropertyParser { 14 | class func parse(from properties: String, parentId: MapObjectId) throws -> ListenerChain? { 15 | var chain: ListenerChain = [] 16 | let lines = properties.components(separatedBy: "\n") 17 | for line in lines { 18 | let params = line.components(separatedBy: ",") 19 | let type = params[0] 20 | let args = Array(params.dropFirst(1)) 21 | do { 22 | chain += try ListenerContainer.getBy(type, directionToParent: nil, params: args) 23 | } catch { 24 | // TODO 25 | } 26 | } 27 | 28 | // For looping animation 29 | chain = chain + [ 30 | (listener: ReloadBehaviorEventListener.self, params: JSON(["eventObjectId":parentId]) as JSON?) 31 | ] 32 | 33 | return chain 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Map/TileSheet/MapObject/util/EventPropertyParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventPropertyParser.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/12/25. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum EventParserError: Error { 12 | case invalidProperty(String) 13 | } 14 | 15 | struct EventProperty { 16 | var type: String 17 | var relativeCoordinates: [TileCoordinate] 18 | var args: [String] 19 | 20 | // FIXME: 斜め方向に対応していない 21 | func calcDirection(from relativeCoordinate: TileCoordinate) -> DIRECTION { 22 | if relativeCoordinate.x > 0 { 23 | return .left 24 | } else if relativeCoordinate.x < 0 { 25 | return .right 26 | } else if relativeCoordinate.y > 0 { 27 | return .down 28 | } else if relativeCoordinate.y < 0 { 29 | return .up 30 | } 31 | 32 | // FIXME: 上記のいずれにもマッチしなかった場合にどうするか 33 | return .down 34 | } 35 | } 36 | 37 | class EventPropertyParser { 38 | class func parse(from eventProperties: String) throws -> [EventProperty] { 39 | let eventProperty = eventProperties.components(separatedBy: "\n") 40 | var properties: [EventProperty] = [] 41 | 42 | for property in eventProperty { 43 | properties.append(try EventPropertyParser.parseProperty(from: property)) 44 | } 45 | 46 | return properties 47 | } 48 | 49 | fileprivate class func parseProperty(from eventProperty: String) throws -> EventProperty { 50 | var property = EventProperty(type: "", relativeCoordinates: [], args: []) 51 | 52 | // プロパティを (イベント種別, イベント配置, イベント引数) に分割 53 | // ex) talk,{(0,-1),(-1,0),(1,0)},params 54 | let eventCoordinatesPattern = "(\\(-?[0-9]+,-?[0-9]+\\),?)+" 55 | let matchedEventCoordinatesSets = EventPropertyParser.matches(for: eventCoordinatesPattern, in: eventProperty) 56 | if matchedEventCoordinatesSets == [] { 57 | throw EventParserError.invalidProperty("Invalid Property: \(eventProperty)") 58 | } 59 | let eventCoordinatesSetString = matchedEventCoordinatesSets[0] 60 | 61 | let matchedEventCoordinates = self.matches(for: "\\(-?[0-9]+,-?[0-9]+\\)", in: eventCoordinatesSetString) 62 | for eventCoordinateString in matchedEventCoordinates { 63 | property.relativeCoordinates.append(TileCoordinate.parse(from: eventCoordinateString)) 64 | } 65 | 66 | let params = eventProperty.replacingOccurrences(of: "{"+eventCoordinatesSetString+"},", with: "") 67 | let tmp = params.components(separatedBy: ",") 68 | property.type = tmp[0] 69 | property.args = Array(tmp.dropFirst()) 70 | 71 | return property 72 | } 73 | 74 | fileprivate class func matches(for regex: String, in text: String) -> [String] { 75 | do { 76 | let regex = try NSRegularExpression(pattern: regex) 77 | let nsString = text as NSString 78 | let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length)) 79 | return results.map { nsString.substring(with: $0.range)} 80 | } catch let error { 81 | print("invalid regex: \(error.localizedDescription)") 82 | return [] 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Map/TileSheet/MapObject/util/ListenerContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventListenerGenerator.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/08/04. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | enum ListenerContainerError: Error { 13 | case eventIdNotFound 14 | case invalidParams(String) 15 | } 16 | 17 | class ListenerContainer { 18 | class func getBy(_ id: String, directionToParent: DIRECTION?, params: [String]) throws -> ListenerChain { 19 | switch id { 20 | case "talk": 21 | let parser = TalkBodyParser(talkFileName: params[0]) 22 | if parser == nil { 23 | throw ListenerContainerError.invalidParams("Specified talk file (\(params[0])) is not found. Check your params (\(params.description) format)") 24 | } 25 | var paramsJson = parser?.parse() 26 | 27 | // direction を追加 28 | var array = paramsJson!.arrayObject as? [[String:String]] 29 | let directionString = directionToParent == nil ? "" : directionToParent!.toString 30 | array!.append(["direction":directionString!]) 31 | paramsJson = JSON(array!) 32 | 33 | return [ 34 | (listener: ActivateButtonListener.self, params: JSON(["text":"はなす"]) as JSON?), 35 | (listener: StartTalkEventListener.self, params: paramsJson), 36 | ] 37 | case "item": 38 | let item = ItemTable.get(params[0]) 39 | if item == nil { 40 | throw ListenerContainerError.invalidParams("Specified item key (\(params[0])) in params(\(params.description)) is not found. Check ItemTable definition") 41 | } 42 | 43 | return [ 44 | (listener: ItemGetEventListener.self, params: item!.getJSON() as JSON?), 45 | (listener: ShowEventDialogListener.self, params: JSON(["text":"test"])), 46 | ] 47 | case "move": 48 | let objectName = params[0] as String 49 | let direction = params[1] as String 50 | let step_num = params[2] as String 51 | let speed = params[3] as String 52 | 53 | return [ 54 | (listener: MoveObjectEventListener.self, 55 | params: JSON([ 56 | "name":objectName, 57 | "direction":direction, 58 | "step_num":step_num, 59 | "speed":speed 60 | ]) as JSON?) 61 | ] 62 | case "wait": 63 | let time = params[0] as String 64 | return [ 65 | (listener: WaitEventListener.self, params: JSON(["time":time])) 66 | ] 67 | case "scene": 68 | let map_file_name = params[0] as String 69 | let player_place_coordiante = params[1] as String 70 | let player_direction = params[2] as String 71 | 72 | return [ 73 | (listener: SceneTransitionEventListener.self, 74 | params: JSON([ 75 | "map_file_name":map_file_name, 76 | "player_place_coordinate":player_place_coordiante, 77 | "player_direction":player_direction 78 | ])) 79 | ] 80 | default: 81 | throw ListenerContainerError.eventIdNotFound 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Map/TileSheet/MapObject/util/ListenerGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGenerator.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2017/01/23. 6 | // Copyright © 2017年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum ListenerGeneratorError: Error { 12 | case failed(String) 13 | } 14 | 15 | class ListenerGenerator { 16 | class func generate(properties: [EventProperty]) throws -> Dictionary { 17 | var listenerChains: Dictionary = [:] 18 | 19 | // イベントプロパティからイベントリスナーを生成する 20 | // relative Coordinates 毎にリスナーチェーンを保存していく 21 | for property in properties { 22 | for relativeCoordinate in property.relativeCoordinates { 23 | do { 24 | let listenerChain = try ListenerContainer.getBy( 25 | property.type, 26 | directionToParent: property.calcDirection(from: relativeCoordinate), 27 | params: property.args) 28 | if listenerChains[relativeCoordinate] != nil { 29 | listenerChains[relativeCoordinate]? += listenerChain 30 | } else { 31 | listenerChains[relativeCoordinate] = listenerChain 32 | } 33 | } catch ListenerContainerError.eventIdNotFound { 34 | throw ListenerGeneratorError.failed("不正なイベントIDです") 35 | } catch ListenerContainerError.invalidParams(let string) { 36 | throw ListenerGeneratorError.failed(string) 37 | } 38 | } 39 | } 40 | 41 | var listeners: Dictionary = [:] 42 | // イベントリスナーの生成 43 | for (coordinate, listenerChain) in listenerChains { 44 | let listenerType = listenerChain.first?.listener 45 | let params = listenerChain.first?.params 46 | do { 47 | // When the listener chain has finished, the player and objects 48 | // should be able to move. 49 | let chain: ListenerChain = ListenerChain(listenerChain.dropFirst(1)) + [ 50 | (listener: BackToDefaultStateEventListener.self, params: nil) 51 | ] 52 | let listener = try listenerType?.init( 53 | params: params, 54 | chainListeners: chain) 55 | listeners[coordinate] = listener 56 | } catch EventListenerError.illegalArguementFormat(let string) { 57 | throw ListenerGeneratorError.failed("Illegal arguement for listener: " + string) 58 | } catch EventListenerError.illegalParamFormat(let array) { 59 | throw ListenerGeneratorError.failed("Illegal parameter for listener: " + array.joined(separator: ",")) 60 | } catch EventListenerError.invalidParam(let string) { 61 | throw ListenerGeneratorError.failed("Invalid parameter for listener: " + string) 62 | } catch EventParserError.invalidProperty(let string) { 63 | throw ListenerGeneratorError.failed("Invalid property for listener: " + string) 64 | } 65 | } 66 | 67 | return listeners 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Map/TileSheet/TileCoordinate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TileCoordinate.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2015/08/10. 6 | // Copyright © 2015年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SpriteKit 12 | 13 | /// タイル座標.座標には以下の三種類がある 14 | /// - タイル座標 TileCoordinate : タイル 15 | /// - 画面座標 ScreenCoordinate : 画面左下を原点とした相対座標 16 | /// - シート座標 SheetCoordinate : シート左下を原点とした相対座標 17 | class TileCoordinate: Hashable { 18 | let x: Int 19 | let y: Int 20 | 21 | init(x: Int, y: Int) { 22 | self.x = x 23 | self.y = y 24 | } 25 | 26 | /// タッチされた位置に最も近いタイルの中心の画面上の座標を返す 27 | /// 28 | /// - parameter sheetPosition: タイルシートの位置 29 | /// - parameter touchedPosition: タッチされた座標 30 | /// 31 | /// - returns: タイルの中心の画面上の座標 32 | class func getSheetCoordinateOfTouchedTile(_ sheetPosition: CGPoint, touchedPosition: CGPoint) -> CGPoint { 33 | return TileCoordinate.getSheetCoordinateFromTileCoordinate( 34 | TileCoordinate.getTileCoordinateFromScreenCoordinate(sheetPosition, screenCoordinate: touchedPosition) 35 | ) 36 | } 37 | 38 | /// 画面座標をタイル座標に変換する 39 | /// 40 | /// - parameter sheetPosition: タイルシートの位置 41 | /// - parameter touchedPosition: タッチされた座標 42 | /// 43 | /// - returns: 最も近いタイルのタイル座標 44 | class func getTileCoordinateFromScreenCoordinate(_ sheetPosition: CGPoint, screenCoordinate: CGPoint) -> TileCoordinate { 45 | return TileCoordinate(x: Int(floor((screenCoordinate.x - sheetPosition.x) / CGFloat(Tile.TILE_SIZE) + 1)), 46 | y: Int(floor((screenCoordinate.y - sheetPosition.y) / CGFloat(Tile.TILE_SIZE) + 1))) 47 | } 48 | 49 | /// シート座標をタイル座標に変換する 50 | /// 51 | /// - parameter sheetPosition: シート座標 52 | /// 53 | /// - returns: タイル座標 54 | class func getTileCoordinateFromSheetCoordinate(_ sheetCoordinate: CGPoint) -> TileCoordinate { 55 | return TileCoordinate(x: Int(floor(sheetCoordinate.x / CGFloat(Tile.TILE_SIZE) + 1)), 56 | y: Int(floor(sheetCoordinate.y / CGFloat(Tile.TILE_SIZE) + 1))) 57 | } 58 | 59 | class func getSheetCoordinateFromScreenCoordinate(_ sheetPosition: CGPoint, screenCoordinate: CGPoint) -> CGPoint { 60 | return CGPoint(x: screenCoordinate.x + sheetPosition.x, y: screenCoordinate.y + sheetPosition.y) 61 | } 62 | 63 | /// タイル座標をシート座標に変換する 64 | /// シート座標は,該当タイルの中心の値を返す 65 | /// 66 | /// - parameter coordinate: タイル座標 67 | /// 68 | /// - returns: シート座標 69 | class func getSheetCoordinateFromTileCoordinate(_ tileCoordinate: TileCoordinate) -> CGPoint { 70 | return CGPoint(x: CGFloat(tileCoordinate.x) * Tile.TILE_SIZE - Tile.TILE_SIZE / 2, 71 | y: CGFloat(tileCoordinate.y) * Tile.TILE_SIZE - Tile.TILE_SIZE / 2) 72 | } 73 | 74 | /// 文字列を座標に変換する 75 | class func parse(from coordinateString: String) -> TileCoordinate { 76 | let coordinateValues = coordinateString 77 | .replacingOccurrences(of: "(", with: "") 78 | .replacingOccurrences(of: ")", with: "") 79 | .components(separatedBy: ",") 80 | let x: Int? = Int(coordinateValues[0]) 81 | let y: Int? = Int(coordinateValues[1]) 82 | 83 | if x == nil || y == nil { 84 | print("Invalid params: \(coordinateString)") 85 | return TileCoordinate(x: 0, y: 0) 86 | } 87 | 88 | return TileCoordinate(x: x!, y: y!) 89 | } 90 | 91 | // MARK: - Hashable 92 | 93 | var hashValue : Int { 94 | get { 95 | return "\(self.x),\(self.y)".hashValue 96 | } 97 | } 98 | 99 | var description : String { 100 | get { 101 | return "\(self.x),\(self.y)" 102 | } 103 | } 104 | } 105 | 106 | // MARK: - Equatable 107 | 108 | func ==(lhs: TileCoordinate, rhs: TileCoordinate) -> Bool { 109 | return lhs.hashValue == rhs.hashValue 110 | } 111 | 112 | func -(lhs: TileCoordinate, rhs: TileCoordinate) -> TileCoordinate { 113 | return TileCoordinate(x: lhs.x-rhs.x, y: lhs.y-rhs.y) 114 | } 115 | 116 | func +(lhs: TileCoordinate, rhs: TileCoordinate) -> TileCoordinate { 117 | return TileCoordinate(x: lhs.x+rhs.x, y: lhs.y+rhs.y) 118 | } 119 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Map/TileSheet/TileSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TileSet.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2016/02/22. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SpriteKit 12 | 13 | enum TileSetError: Error { 14 | case failedToCrop 15 | } 16 | 17 | /// タイルセット : 1画像ファイル内のタイル群 = 1タイルセットに対応 18 | class TileSet { 19 | /// タイルセットを一意に識別するID 20 | fileprivate let tileSetID: Int! 21 | 22 | /// 画像ファイル名 23 | fileprivate let imageName: String! 24 | 25 | /// セット内のタイル数 26 | fileprivate let count: Int! 27 | 28 | /// 一番若いタイルID 29 | fileprivate let firstTileID: Int! 30 | 31 | /// タイルセット(画像ファイル)の横幅 32 | fileprivate let imageWidth: Int! 33 | 34 | /// タイルセット(画像ファイル)の縦幅 35 | fileprivate let imageHeight: Int! 36 | 37 | /// タイルセット内の各タイルの横幅 38 | fileprivate let tileWidth: Int! 39 | 40 | /// タイルセット内の各タイルの縦幅 41 | fileprivate let tileHeight: Int! 42 | 43 | init?(id: Int, 44 | imageName: String, 45 | nTile: Int, 46 | firstTileID: Int, 47 | width: Int, 48 | height: Int, 49 | tileWidth: Int, 50 | tileHeight: Int) { 51 | self.tileSetID = id 52 | self.imageName = imageName 53 | self.count = nTile 54 | self.firstTileID = firstTileID 55 | self.imageWidth = width 56 | self.imageHeight = height 57 | self.tileWidth = tileWidth 58 | self.tileHeight = tileHeight 59 | } 60 | 61 | /// タイルの画像を,タイルセット(1画像ファイル)から切り出し,返す 62 | /// 63 | /// - parameter tileSetID: 対象タイルが含まれるタイルセットID 64 | /// - parameter tileID: 対象タイルのタイルID 65 | /// 66 | /// - throws: otherError 67 | /// 68 | /// - returns: タイル画像 69 | func cropTileImage(_ tileID: Int) throws -> UIImage { 70 | let tileSetRows = self.imageWidth / self.tileWidth 71 | let firstTileID = self.firstTileID 72 | var iTargetTileInSet: Int 73 | 74 | // ID は左上から順番 75 | // TODO: tileSet の中に tileID が含まれていない場合の validation 76 | if firstTileID! >= tileID { 77 | iTargetTileInSet = firstTileID! - tileID 78 | } else { 79 | iTargetTileInSet = tileID - 1 80 | } 81 | 82 | // 対象タイルの,タイルセット内における位置(行数,列数)を調べる 83 | let targetCol: Int 84 | let targetRow: Int 85 | if iTargetTileInSet == 0 { 86 | targetCol = 1 87 | targetRow = 1 88 | } else { 89 | targetRow = Int(iTargetTileInSet % tileSetRows) + 1 90 | targetCol = Int(iTargetTileInSet / tileSetRows) + 1 91 | } 92 | 93 | // 画像の切り抜き 94 | let tileSize = CGRect( 95 | x: CGFloat(tileWidth) * CGFloat(targetRow - 1), 96 | y: CGFloat(tileHeight) * CGFloat(targetCol - 1), 97 | width: CGFloat(tileWidth), 98 | height: CGFloat(tileHeight)) 99 | if let image = UIImage(named: self.imageName), 100 | let cropCGImageRef = image.cgImage?.cropping(to: tileSize) { 101 | return UIImage(cgImage: cropCGImageRef) 102 | } else { 103 | throw TileSetError.failedToCrop 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Map/TiledMapJsonParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TiledMapJsonParser.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2015/10/12. 6 | // Copyright © 2015年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import SpriteKit 12 | 13 | enum ParseError: Error { 14 | case jsonFileNotFound 15 | case illegalJsonFormat 16 | case swiftyJsonError([NSError?]) 17 | case invalidValueError(String) 18 | } 19 | 20 | /// タイルの配置や種類の情報を記述したJSONファイルのパーサ 21 | class TiledMapJsonParser { 22 | fileprivate var json : JSON = JSON.null 23 | 24 | /// タイルサイズ 25 | fileprivate let TILE_SIZE = Int(Tile.TILE_SIZE) 26 | 27 | /// コンストラクタ 28 | /// 29 | /// - throws: JsonFileNotFound, IllegalJsonFormat 30 | /// 31 | /// - parameter fileName: パース対象のjsonファイル名 32 | init(fileName: String) throws { 33 | let path: String? = Bundle.main.path(forResource: fileName, ofType: "json") 34 | if path == nil { 35 | throw ParseError.jsonFileNotFound 36 | } 37 | 38 | let fileHandle: FileHandle? = FileHandle(forReadingAtPath: path!) 39 | if fileHandle == nil { 40 | throw ParseError.jsonFileNotFound 41 | } 42 | 43 | let data: Data = fileHandle!.readDataToEndOfFile() 44 | 45 | self.json = JSON(data: data) 46 | if self.json.type != Type.dictionary { 47 | throw ParseError.illegalJsonFormat 48 | } 49 | } 50 | 51 | /// タイルセットの各情報を取得する 52 | /// 53 | /// - throws: SwiftyJsonError 54 | /// 55 | /// - returns: タイルセットの情報.画像ファイル名やセット内のタイル数等.詳しくは TileSetInfo を参照 56 | func getTileSets() throws -> Dictionary { 57 | var tileSets: Dictionary = [:] 58 | 59 | let tileSetsJson = json["tilesets"].array 60 | if tileSetsJson == nil { 61 | throw ParseError.swiftyJsonError([json["tilesets"].error]) 62 | } 63 | 64 | var tileSetID = 1 65 | for tileSetJson in tileSetsJson! { 66 | // パスからファイル名を抽出 67 | let imageName = tileSetJson["image"].string!.components(separatedBy: "/").last! 68 | tileSets[tileSetID] = TileSet(id: tileSetID, 69 | imageName: imageName, 70 | nTile: tileSetJson["tilecount"].int!, 71 | firstTileID: tileSetJson["firstgid"].int!, 72 | width: tileSetJson["imagewidth"].int!, 73 | height: tileSetJson["imageheight"].int!, 74 | tileWidth: tileSetJson["tilewidth"].int!, 75 | tileHeight: tileSetJson["tileheight"].int!) 76 | tileSetID += 1 77 | } 78 | return tileSets 79 | } 80 | 81 | /// 各タイルのプロパティを取得する 82 | /// 83 | /// - throws: SwiftyJsonError, otherError 84 | /// 85 | /// - returns: プロパティのディクショナリ 86 | func getTileProperties() throws -> Dictionary { 87 | var properties: Dictionary = [:] 88 | 89 | let tileSets = json["tilesets"].array 90 | if tileSets == nil { 91 | throw ParseError.swiftyJsonError([json["tilesets"].error]) 92 | } 93 | 94 | var tileSetID = 1 95 | for tileSet in tileSets! { 96 | /// tileSet 内の最初のタイルの gid 97 | let firstTileID = tileSet["firstgid"].int 98 | /// tileSet 内に存在するタイルの数 99 | let nTileInSet = tileSet["tilecount"].int 100 | if firstTileID == nil || nTileInSet == nil { 101 | throw ParseError.swiftyJsonError([tileSet["firstgid"].error, tileSet["tilecount"].error]) 102 | } 103 | 104 | // tileSet 内の各タイルについて,そのプロパティに tileSet の情報を追加する 105 | for tileID in firstTileID! ... firstTileID! + nTileInSet! - 1 { 106 | properties[tileID] = [ 107 | "tileSetID": tileSetID.description, 108 | "tileSetName": tileSet["name"].string! 109 | ] 110 | } 111 | 112 | // 各タイル毎にその他のプロパティを保持 113 | if tileSet["tileproperties"] == JSON.null { 114 | throw ParseError.swiftyJsonError([tileSet["tileproperties"].error]) 115 | } 116 | for (cor, tileproperties) in tileSet["tileproperties"] { 117 | for (property, value) in tileproperties { 118 | let tileID = firstTileID! + Int(cor)! 119 | if properties[tileID] != nil { 120 | properties[tileID]![property] = value.string 121 | } else { 122 | throw ParseError.invalidValueError("TileID is not found in properties") 123 | } 124 | } 125 | } 126 | tileSetID += 1 127 | } 128 | return properties 129 | } 130 | 131 | /// レイヤーの種類を表す enum 型 132 | /// 133 | /// - TILE: タイル情報 134 | /// - COLLISION: 当たり判定情報 135 | /// - OBJECT: オブジェクト情報 136 | enum LAYER: Int { 137 | case tile = 0 138 | case collision = 1 139 | case object = 2 140 | } 141 | 142 | /// レイヤから得られる情報(タイルの配置情報)を返す 143 | /// 144 | /// - parameter layerTileCols: レイヤ上のタイルの行数 145 | /// - parameter layerTileRows: レイヤ上のタイルの列数 146 | /// - parameter kind: 読み込むレイヤの種類 147 | /// 148 | /// - throws: SwiftyJsonError, otherError 149 | /// 150 | /// - returns: レイヤ情報(タイル座標とタイルIDの組) 151 | func getInfoFromLayer( 152 | _ layerTileCols: Int, 153 | layerTileRows: Int, 154 | kind: LAYER 155 | ) throws -> Dictionary { 156 | var info: Dictionary = [:] 157 | 158 | if (layerTileCols < 1 || layerTileRows < 1) { 159 | throw ParseError.invalidValueError("Invalid layer size: cols or rows is fewer than 1") 160 | } 161 | 162 | let layer = json["layers"][kind.rawValue]["data"].array 163 | if layer == nil { 164 | throw ParseError.swiftyJsonError([json["layers"][kind.rawValue]["data"].error]) 165 | } 166 | 167 | for y in 1 ..< layerTileCols+1 { 168 | for x in 1 ..< layerTileRows+1 { 169 | let index = (layerTileCols - y) * layerTileRows + x - 1 170 | if layer![index].int == nil { 171 | throw ParseError.swiftyJsonError([layer![index].error]) 172 | } 173 | info[TileCoordinate(x: x, y: y)] = layer![index].int! 174 | } 175 | } 176 | 177 | return info 178 | } 179 | 180 | /// レイヤーのサイズを取得する 181 | /// 182 | /// - throws: SwiftyJsonError 183 | /// 184 | /// - returns: [ 行数, 列数 ] 185 | func getLayerSize() throws -> (cols: Int, rows: Int) { 186 | let layerTileCols = json["height"].int 187 | let layerTileRows = json["width"].int 188 | if layerTileCols == nil || layerTileRows == nil { 189 | throw ParseError.swiftyJsonError([json["height"].error, json["width"].error]) 190 | } 191 | 192 | return (layerTileCols!, layerTileRows!) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/MenuSceneModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuSceneModel.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/07/29. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SwiftyJSON 12 | import RealmSwift 13 | 14 | protocol MenuSceneModelDelegate { 15 | func updateItemSelect() 16 | func reloadTable() 17 | } 18 | 19 | class MenuSceneModel: NSObject, UICollectionViewDataSource { 20 | var delegate: MenuSceneModelDelegate! 21 | fileprivate(set) var selectedContents: JSON? = nil 22 | let defaultMessage = "...。" 23 | fileprivate(set) var deselectedIndexPath: IndexPath? = nil 24 | fileprivate(set) var selectedIndexPath: IndexPath? = nil 25 | fileprivate(set) var contents: [Item] = [] 26 | 27 | func updateItems() { 28 | let realm = try! Realm() 29 | let items = realm.objects(StoredItems.self) 30 | for item in items { 31 | contents.append(Item(key: item.key, name: item.name, description: item.text, image_name: item.image_name)) 32 | } 33 | self.delegate.reloadTable() 34 | } 35 | 36 | func selectItem(_ indexPath: IndexPath) { 37 | self.deselectedIndexPath = self.selectedIndexPath 38 | self.selectedIndexPath = indexPath 39 | self.delegate.updateItemSelect() 40 | } 41 | 42 | // MARK: UICollectionViewDataSource 43 | 44 | func numberOfSections(in collectionView: UICollectionView) -> Int { 45 | return 1 46 | } 47 | 48 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 49 | return self.contents.count 50 | } 51 | 52 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 53 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ItemCell 54 | cell.imageView.image = UIImage(named: contents[indexPath.row].image_name) 55 | return cell 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Setting/ItemTable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemTable.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/08/04. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | struct Item { 13 | var key: String 14 | var name: String 15 | var description: String 16 | var image_name: String 17 | 18 | func getJSON() -> JSON { 19 | return JSON([ 20 | "key": self.key, 21 | "name": self.name, 22 | "description": self.description, 23 | "image_name": self.image_name 24 | ]) 25 | } 26 | } 27 | 28 | struct ItemTable { 29 | static func get(_ key: String) -> Item? { 30 | for item in ItemTable.items { 31 | if item.key == key { 32 | return item 33 | } 34 | } 35 | return nil 36 | } 37 | 38 | static let items: [Item] = [ 39 | Item(key: "test", name: "test object", description: "テスト用アイテムです", image_name: "kanamono_tile.png") 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Setting/MapTable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapTable.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2017/01/29. 6 | // Copyright © 2017年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct MapTable { 12 | static let fromJsonFileName: Dictionary = [ 13 | "sample_map02.json": FirstGameScene.self, 14 | "sample_map01.json": SecondGameScene.self 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Setting/objectNameTable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // objectNameTable.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2016/02/23. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct objectNameTable { 12 | static let PLAYER_NAME = "tasuwo" 13 | 14 | static let PLAYER_IMAGE_UP = "plr_up.png" 15 | static let PLAYER_IMAGE_DOWN = "plr_down.png" 16 | static let PLAYER_IMAGE_RIGHT = "plr_right.png" 17 | static let PLAYER_IMAGE_LEFT = "plr_left.png" 18 | static let PLAYER_IMAGE_SET = IMAGE_SET( 19 | UP: 20 | [ 21 | ["plr_up_01.png", "plr_up.png"], 22 | ["plr_up_02.png", "plr_up.png"] 23 | ], 24 | DOWN: 25 | [ 26 | ["plr_down_01.png", "plr_down.png"], 27 | ["plr_down_02.png", "plr_down.png"] 28 | ], 29 | RIGHT: 30 | [ 31 | ["plr_right_01.png", "plr_right.png"], 32 | ["plr_right_02.png", "plr_right.png"] 33 | ], 34 | LEFT: 35 | [ 36 | ["plr_left_01.png", "plr_left.png"], 37 | ["plr_left_02.png", "plr_left.png"] 38 | ] 39 | ) 40 | 41 | static func getImageBy(direction: DIRECTION) -> String { 42 | switch direction { 43 | case .up: 44 | return objectNameTable.PLAYER_IMAGE_UP 45 | case .down: 46 | return objectNameTable.PLAYER_IMAGE_DOWN 47 | case .right: 48 | return objectNameTable.PLAYER_IMAGE_RIGHT 49 | case .left: 50 | return objectNameTable.PLAYER_IMAGE_LEFT 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Setting/talkerImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // talkerList.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2016/02/26. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public let TALKER_IMAGE = [ 12 | "player": "player.png" 13 | ] -------------------------------------------------------------------------------- /SwiftRPG/src/Model/Setting/zPositionTable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // zPosition.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2016/02/24. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SpriteKit 12 | 13 | class zPositionTable { 14 | static let TILE: CGFloat = 0.0 15 | static let BASE_OBJECT_POSITION: CGFloat = 2.0 16 | static let FLAME: CGFloat = 1001.0 17 | static let DIALOG: CGFloat = 1002.0 18 | static let DIALOG_ICON: CGFloat = 1003.0 19 | } -------------------------------------------------------------------------------- /SwiftRPG/src/Model/common/DIRECTION.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DIRECTION.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/08/04. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum DIRECTION { 12 | case up, down, left, right 13 | 14 | var toString : String! { 15 | switch self { 16 | case .up: 17 | return "UP" 18 | case .down: 19 | return "DOWN" 20 | case .left: 21 | return "LEFT" 22 | case .right: 23 | return "RIGHT" 24 | } 25 | } 26 | 27 | var reverse: DIRECTION { 28 | switch self { 29 | case .up: 30 | return .down 31 | case .down: 32 | return .up 33 | case .left: 34 | return .right 35 | case .right: 36 | return .left 37 | } 38 | } 39 | 40 | static func fromString(_ direction: String) -> DIRECTION? { 41 | switch direction { 42 | case "UP": 43 | return DIRECTION.up 44 | case "DOWN": 45 | return DIRECTION.down 46 | case "RIGHT": 47 | return DIRECTION.right 48 | case "LEFT": 49 | return DIRECTION.left 50 | default: 51 | return nil 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/common/DialogLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DialogLabel.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2016/03/18. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class DialogLabel: UILabel { 13 | 14 | let padding = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) 15 | 16 | override func drawText(in rect: CGRect) { 17 | let newRect = UIEdgeInsetsInsetRect(rect, padding) 18 | super.drawText(in: newRect) 19 | } 20 | 21 | override var intrinsicContentSize : CGSize { 22 | var intrinsicContentSize = super.intrinsicContentSize 23 | intrinsicContentSize.height += padding.top + padding.bottom 24 | intrinsicContentSize.width += padding.left + padding.right 25 | return intrinsicContentSize 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/common/IMAGE_SET.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IMAGE_SET.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/08/04. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct IMAGE_SET { 12 | let UP: [[String]] 13 | let DOWN: [[String]] 14 | let RIGHT: [[String]] 15 | let LEFT: [[String]] 16 | 17 | func get(_ direction: DIRECTION) -> [[String]] { 18 | switch direction { 19 | case .up: 20 | return self.UP 21 | case .down: 22 | return self.DOWN 23 | case .left: 24 | return self.LEFT 25 | case .right: 26 | return self.RIGHT 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/common/String+subscript.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+subscript.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/08/04. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | 13 | subscript (i: Int) -> Character { 14 | return self[self.characters.index(self.startIndex, offsetBy: i)] 15 | } 16 | 17 | subscript (i: Int) -> String { 18 | return String(self[i] as Character) 19 | } 20 | 21 | subscript (r: Range) -> String { 22 | let start = characters.index(startIndex, offsetBy: r.lowerBound) 23 | let end = characters.index(start, offsetBy: r.upperBound - r.lowerBound) 24 | //let start = startIndex.advancedBy(r.startIndex) 25 | //let end = start.advancedBy(r.endIndex - r.startIndex) 26 | return self[Range(start ..< end)] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/common/UIButton+title.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+title.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/12/24. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIButton { 13 | var title: String? { 14 | get { 15 | return self.title(for: .normal) 16 | } 17 | set(v) { 18 | UIView.performWithoutAnimation { 19 | self.setTitle(v, for: .normal) 20 | self.layoutIfNeeded() 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/common/UIButtonAnimated.swift: -------------------------------------------------------------------------------- 1 | // 2 | // common.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2015/07/16. 6 | // Copyright (c) 2015年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpriteKit 11 | import Foundation 12 | 13 | class UIButtonAnimated: UIButton { 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | } 18 | 19 | convenience init() { 20 | self.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) 21 | } 22 | 23 | required init(coder aDecoder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 28 | super.touchesBegan(touches, with: event) 29 | self.touchStartAnimation() 30 | } 31 | 32 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) { 33 | super.touchesCancelled(touches, with: event) 34 | self.touchEndAnimation() 35 | } 36 | 37 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 38 | super.touchesEnded(touches, with: event) 39 | self.touchEndAnimation() 40 | } 41 | 42 | fileprivate func touchStartAnimation() { 43 | UIView.animate(withDuration: 0.1, 44 | delay: 0.0, 45 | options: UIViewAnimationOptions.curveEaseIn, 46 | animations: { 47 | () -> Void in 48 | self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95); 49 | self.alpha = 0.7 50 | }, 51 | completion: nil) 52 | } 53 | 54 | fileprivate func touchEndAnimation() { 55 | UIView.animate(withDuration: 0.1, 56 | delay: 0.0, 57 | options: UIViewAnimationOptions.curveEaseIn, 58 | animations: { 59 | () -> Void in 60 | self.transform = CGAffineTransform(scaleX: 1.0, y: 1.0); 61 | self.alpha = 1 62 | }, 63 | completion: nil) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /SwiftRPG/src/Model/common/myButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // myButton.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/07/29. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpriteKit 11 | import Foundation 12 | 13 | class myButton: UIButton, NSCopying { 14 | 15 | func setTitle(_ title: String?) { 16 | setTitle(title, for: UIControlState()) 17 | setTitle(title, for: UIControlState.highlighted) 18 | } 19 | 20 | func copy(with zone: NSZone?) -> Any { 21 | let newInstance = myButton() 22 | 23 | newInstance.frame = self.frame 24 | newInstance.backgroundColor = self.backgroundColor 25 | newInstance.layer.masksToBounds = self.layer.masksToBounds 26 | newInstance.layer.cornerRadius = self.layer.cornerRadius 27 | // 文字色 28 | newInstance.setTitleColor( 29 | self.titleColor(for: UIControlState()), 30 | for: UIControlState()) 31 | newInstance.setTitleColor( 32 | self.titleColor(for: UIControlState.highlighted), 33 | for: UIControlState.highlighted) 34 | 35 | return newInstance 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /SwiftRPG/src/View/FirstGameScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // myGameScene.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/12/22. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SpriteKit 11 | import UIKit 12 | 13 | class FirstGameScene: GameScene { 14 | required init(size: CGSize, playerCoordiante: TileCoordinate, playerDirection: DIRECTION) { 15 | super.init(size: size, playerCoordiante: playerCoordiante, playerDirection: playerDirection) 16 | } 17 | 18 | required init?(coder aDecoder: NSCoder) { 19 | fatalError("init(coder:) has not been implemented") 20 | } 21 | 22 | override func didMove(to view: SKView) { 23 | super.didMove(to: view) 24 | 25 | /* 地形の読み込み */ 26 | if let map = Map(mapName: "sample_map02", frameWidth: self.frame.width, frameHeight: self.frame.height) { 27 | self.map = map 28 | self.map!.addSheetTo(self) 29 | } else { 30 | // TODO: Alert to user and quit game 31 | print("Failed to generate map!!") 32 | return 33 | } 34 | 35 | // 主人公の作成 36 | let playerInitialPosition = TileCoordinate.getSheetCoordinateFromTileCoordinate(self.playerInitialCoordinate!) 37 | let player = Object(name: objectNameTable.PLAYER_NAME, 38 | imageName: objectNameTable.getImageBy(direction: self.playerInitialDirection!), 39 | position: playerInitialPosition, 40 | images: objectNameTable.PLAYER_IMAGE_SET) 41 | player.setCollision() 42 | self.map!.setObject(player) 43 | self.startBehaviors() 44 | 45 | // Config sheet's position 46 | self.map?.sheet?.centerOn(point: player.position, frameWidth: self.frame.width, frameHeight: self.frame.height) 47 | 48 | self.gameSceneDelegate?.startWalking() 49 | 50 | actionButton.isHidden = true 51 | 52 | textBox = Dialog(frame_width: self.frame.width, frame_height: self.frame.height) 53 | textBox.hide() 54 | textBox.setPositionY(Dialog.POSITION.top) 55 | textBox.addTo(self) 56 | 57 | eventDialog.isHidden = true 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /SwiftRPG/src/View/MenuScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuScene.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2016/03/18. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SwiftyJSON 12 | import SpriteKit 13 | 14 | protocol MenuSceneDelegate { 15 | func didPressBackButton() 16 | func didSelectedItem(_ indexPath: IndexPath) 17 | } 18 | 19 | class MenuScene: SKScene { 20 | var menuSceneDelegate: MenuSceneDelegate? 21 | var model: MenuSceneModel! { 22 | didSet { 23 | self.contentsView.dataSource = self.model 24 | self.dialog.text = self.model!.defaultMessage 25 | } 26 | } 27 | fileprivate static let SELECTED_ALPHA: CGFloat = 1.0 28 | fileprivate static let DESELECTED_ALPHA: CGFloat = 0.5 29 | 30 | @IBOutlet var sceneView: SKView! 31 | @IBOutlet weak var baseView: UIView! 32 | @IBOutlet weak var backButton: UIButton! 33 | @IBOutlet weak var dialog: UILabel! 34 | @IBOutlet weak var contentsView: UICollectionView! 35 | @IBAction func didPressBackButton(_ sender: AnyObject) { 36 | self.menuSceneDelegate?.didPressBackButton() 37 | } 38 | 39 | override init(size: CGSize) { 40 | super.init(size: size) 41 | 42 | Bundle.main.loadNibNamed("MenuScene", owner: self, options: nil) 43 | self.view?.addSubview(baseView) 44 | 45 | contentsView.delegate = self 46 | contentsView.register(ItemCell.self, forCellWithReuseIdentifier: "cell") 47 | 48 | dialog.layer.borderColor = UIColor.white.cgColor 49 | } 50 | 51 | required init?(coder aDecoder: NSCoder) { 52 | fatalError("init(coder:) has not been implemented") 53 | } 54 | } 55 | 56 | extension MenuScene: MenuSceneModelDelegate { 57 | func updateItemSelect() { 58 | let selectedCell = self.contentsView.cellForItem(at: self.model.selectedIndexPath!) as! ItemCell 59 | 60 | // 選択されたセル以外の全てのセルを非選択にする 61 | for cell in self.contentsView.visibleCells as! [ItemCell] { 62 | if cell == selectedCell { continue } 63 | cell.imageView.alpha = MenuScene.DESELECTED_ALPHA 64 | } 65 | 66 | // 選択されたセルとテキストボックスの描画更新 67 | if selectedCell.imageView.alpha == MenuScene.SELECTED_ALPHA { 68 | selectedCell.imageView.alpha = MenuScene.DESELECTED_ALPHA 69 | self.dialog.text = self.model.defaultMessage 70 | } else { 71 | selectedCell.imageView.alpha = MenuScene.SELECTED_ALPHA 72 | self.dialog.text = self.model.contents[self.model.selectedIndexPath!.row].description 73 | } 74 | } 75 | 76 | func reloadTable() { 77 | self.contentsView.reloadData() 78 | } 79 | } 80 | 81 | extension MenuScene: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { 82 | 83 | // MARK: UICollectionViewDelegate 84 | 85 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 86 | self.menuSceneDelegate?.didSelectedItem(indexPath) 87 | } 88 | 89 | // MARK: UICollectionViewDelegateFlowLayout 90 | 91 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 92 | return CGSize(width: 40, height: 40) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /SwiftRPG/src/View/MenuScene.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 47 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /SwiftRPG/src/View/TitleScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TitleScene.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2015/07/12. 6 | // Copyright (c) 2015年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpriteKit 11 | import Foundation 12 | 13 | protocol TitleSceneDelegate: class { 14 | func newGameTouched() 15 | } 16 | 17 | class TitleScene: UIView { 18 | var titleSceneDelegate: TitleSceneDelegate? 19 | 20 | @IBOutlet var titleScene: UIView! 21 | @IBOutlet weak var startBtn: UIButton! 22 | 23 | @IBAction func startBtnPressed(_ sender: AnyObject) { 24 | self.titleSceneDelegate?.newGameTouched() 25 | } 26 | 27 | override init(frame: CGRect) { 28 | super.init(frame: frame) 29 | 30 | Bundle.main.loadNibNamed("TitleScene", owner: self, options: nil) 31 | titleScene.frame = frame 32 | addSubview(titleScene) 33 | 34 | titleScene.backgroundColor = UIColor.black 35 | 36 | UIApplication.shared.isNetworkActivityIndicatorVisible = true 37 | } 38 | 39 | required init?(coder aDecoder: NSCoder) { 40 | fatalError("init(coder:) has not been implemented") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /SwiftRPG/src/View/TitleScene.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 29 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /SwiftRPG/src/View/animation/TransitionToMenuAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransitionToMenuAnimator.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2016/08/16. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TransitionBetweenGameAndMenuSceneAnimator: NSObject, UIViewControllerAnimatedTransitioning { 12 | let duration = 0.6 13 | var presenting = true 14 | var originFrame = CGRect.zero 15 | 16 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?)-> TimeInterval { 17 | return duration 18 | } 19 | 20 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 21 | let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) 22 | let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) 23 | 24 | let toView = toVC?.view! 25 | let fromView = fromVC?.view! 26 | toView!.frame = originFrame 27 | toView!.alpha = 0.0 28 | 29 | let backgroundView = UIView(frame: originFrame) 30 | backgroundView.backgroundColor = UIColor.black 31 | 32 | transitionContext.containerView.addSubview(backgroundView) 33 | transitionContext.containerView.addSubview(toView!) 34 | 35 | UIView.animate( 36 | withDuration: duration/2.0, 37 | delay: 0.0, 38 | options: [], 39 | animations: { 40 | fromView?.alpha = 0.0 41 | }, 42 | completion: { 43 | _ in 44 | UIView.animate( 45 | withDuration: self.duration/2.0, 46 | delay: 0.0, 47 | options: [], 48 | animations: { 49 | toView!.alpha = 1.0 50 | }, 51 | completion: { 52 | _ in 53 | transitionContext.completeTransition(true) 54 | } 55 | ) 56 | } 57 | ) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /SwiftRPG/src/View/components/ItemCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemCell.swift 3 | // SwiftRPG 4 | // 5 | // Created by 兎澤佑 on 2016/03/18. 6 | // Copyright © 2016年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class ItemCell: UICollectionViewCell { 13 | @IBOutlet var cellView: UICollectionViewCell! 14 | @IBOutlet weak var imageView: UIImageView! 15 | 16 | override init(frame: CGRect) { 17 | super.init(frame: frame) 18 | Bundle.main.loadNibNamed("ItemCell", owner: self, options: nil) 19 | imageView.backgroundColor = UIColor.black 20 | imageView.alpha = 0.5 21 | addSubview(imageView) 22 | } 23 | 24 | required init?(coder aDecoder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SwiftRPG/src/View/components/ItemCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /SwiftRPG/src/View/components/SecondGameScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // nextGameScene.swift 3 | // SwiftRPG 4 | // 5 | // Created by tasuku tozawa on 2017/01/29. 6 | // Copyright © 2017年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SpriteKit 11 | import UIKit 12 | 13 | class SecondGameScene: GameScene { 14 | required init(size: CGSize, playerCoordiante: TileCoordinate, playerDirection: DIRECTION) { 15 | super.init(size: size, playerCoordiante: playerCoordiante, playerDirection: playerDirection) 16 | } 17 | 18 | required init?(coder aDecoder: NSCoder) { 19 | fatalError("init(coder:) has not been implemented") 20 | } 21 | 22 | override func didMove(to view: SKView) { 23 | super.didMove(to: view) 24 | 25 | if let map = Map(mapName: "sample_map01", frameWidth: self.frame.width, frameHeight: self.frame.height) { 26 | self.map = map 27 | self.map!.addSheetTo(self) 28 | } else { 29 | return 30 | } 31 | 32 | let playerInitialPosition = TileCoordinate.getSheetCoordinateFromTileCoordinate(self.playerInitialCoordinate!) 33 | let player = Object(name: objectNameTable.PLAYER_NAME, 34 | imageName: objectNameTable.getImageBy(direction: self.playerInitialDirection!), 35 | position: playerInitialPosition, 36 | images: objectNameTable.PLAYER_IMAGE_SET) 37 | player.setCollision() 38 | self.map!.setObject(player) 39 | self.startBehaviors() 40 | 41 | self.map?.sheet?.centerOn(point: player.position, frameWidth: self.frame.width, frameHeight: self.frame.height) 42 | 43 | self.gameSceneDelegate?.startWalking() 44 | 45 | actionButton.isHidden = true 46 | 47 | textBox = Dialog(frame_width: self.frame.width, frame_height: self.frame.height) 48 | textBox.hide() 49 | textBox.setPositionY(Dialog.POSITION.top) 50 | textBox.addTo(self) 51 | 52 | eventDialog.isHidden = true 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /SwiftRPGTests/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftRPGTests/SwiftRPGTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftRPGTests.swift 3 | // SwiftRPGTests 4 | // 5 | // Created by 兎澤佑 on 2015/06/27. 6 | // Copyright (c) 2015年 兎澤佑. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class SwiftRPGTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /readme_resources/movie.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tasuwo/SwiftRPG/65a2582b60e80083d14baf721a3ddd7e38aa8093/readme_resources/movie.gif --------------------------------------------------------------------------------