├── player_model_screenshot.png ├── fighter_sprites ├── Resources │ ├── fighter.psd │ ├── missile.lip │ ├── missile.psd │ ├── villain.psd │ ├── fighter_2.psd │ ├── fighter_small.psd │ ├── missile.lip.meta │ ├── fighter_2.psd.meta │ ├── fighter_small.psd.meta │ ├── fighter.psd.meta │ ├── missile.psd.meta │ └── villain.psd.meta └── Resources.meta ├── .gitmodules ├── fighter.unity.meta ├── fighter_tutorial ├── core.clj.meta └── core.clj ├── Arcadia.meta ├── README.md.meta ├── fighter_sprites.meta ├── fighter_tutorial.meta ├── prefabs.meta ├── prefabs ├── Resources.meta └── Resources │ ├── fighter.prefab.meta │ ├── villain.prefab.meta │ ├── missile.prefab.meta │ ├── missile.prefab │ ├── villain.prefab │ └── fighter.prefab ├── player_model_screenshot.png.meta ├── fighter.unity └── README.md /player_model_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcadia-unity/fighter-tutorial/HEAD/player_model_screenshot.png -------------------------------------------------------------------------------- /fighter_sprites/Resources/fighter.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcadia-unity/fighter-tutorial/HEAD/fighter_sprites/Resources/fighter.psd -------------------------------------------------------------------------------- /fighter_sprites/Resources/missile.lip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcadia-unity/fighter-tutorial/HEAD/fighter_sprites/Resources/missile.lip -------------------------------------------------------------------------------- /fighter_sprites/Resources/missile.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcadia-unity/fighter-tutorial/HEAD/fighter_sprites/Resources/missile.psd -------------------------------------------------------------------------------- /fighter_sprites/Resources/villain.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcadia-unity/fighter-tutorial/HEAD/fighter_sprites/Resources/villain.psd -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Arcadia"] 2 | path = Arcadia 3 | url = https://github.com/arcadia-unity/Arcadia.git 4 | branch = defmutable-extension 5 | -------------------------------------------------------------------------------- /fighter_sprites/Resources/fighter_2.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcadia-unity/fighter-tutorial/HEAD/fighter_sprites/Resources/fighter_2.psd -------------------------------------------------------------------------------- /fighter_sprites/Resources/fighter_small.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcadia-unity/fighter-tutorial/HEAD/fighter_sprites/Resources/fighter_small.psd -------------------------------------------------------------------------------- /fighter.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3818031195f6543e082f589328c5db41 3 | timeCreated: 1511205039 4 | licenseType: Free 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /fighter_tutorial/core.clj.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4ea30c938983944df80b6c0174062d51 3 | timeCreated: 1511206676 4 | licenseType: Free 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Arcadia.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3a43c8ce33a4a47b28ebc7a8c75a60f8 3 | folderAsset: yes 4 | timeCreated: 1511206240 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /fighter_sprites/Resources/missile.lip.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 54bd968b9e591456290d85c923426a70 3 | timeCreated: 1511636127 4 | licenseType: Free 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5ce17c253c52a42ce8fdabae36bcc249 3 | timeCreated: 1513264126 4 | licenseType: Free 5 | DefaultImporter: 6 | externalObjects: {} 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /fighter_sprites.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 102a58ff357844f7bb1dfa00c8f4fdf7 3 | folderAsset: yes 4 | timeCreated: 1511204088 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /fighter_tutorial.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 93838c9f4c56342fcad85d8f9b090201 3 | folderAsset: yes 4 | timeCreated: 1511206676 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /fighter_sprites/Resources.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fcf33c3c3496f414e9cfcce4e086ff17 3 | folderAsset: yes 4 | timeCreated: 1511204088 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /prefabs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b0ac6f0ef047448a9817c74a7bc278c6 3 | folderAsset: yes 4 | timeCreated: 1511659812 5 | licenseType: Free 6 | DefaultImporter: 7 | externalObjects: {} 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /prefabs/Resources.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e658538ba3f834c0db83995d67fab220 3 | folderAsset: yes 4 | timeCreated: 1511659880 5 | licenseType: Free 6 | DefaultImporter: 7 | externalObjects: {} 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /prefabs/Resources/fighter.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bdbdfc4cd6c5148398966044e89e23b5 3 | timeCreated: 1511214080 4 | licenseType: Free 5 | NativeFormatImporter: 6 | mainObjectFileID: 100100000 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /prefabs/Resources/villain.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c8e95435c741b4999bbdbdaf911cfcbe 3 | timeCreated: 1511366646 4 | licenseType: Free 5 | NativeFormatImporter: 6 | mainObjectFileID: 100100000 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /prefabs/Resources/missile.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1d2e4956d4887491eb094a3dae73d9b0 3 | timeCreated: 1511659679 4 | licenseType: Free 5 | NativeFormatImporter: 6 | externalObjects: {} 7 | mainObjectFileID: 100100000 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /fighter_sprites/Resources/fighter_2.psd.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 79b309880cfd946efb4d82702616e353 3 | timeCreated: 1511634583 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 4 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 0 11 | sRGBTexture: 1 12 | linearTexture: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapsPreserveCoverage: 0 16 | alphaTestReferenceValue: 0.5 17 | mipMapFadeDistanceStart: 1 18 | mipMapFadeDistanceEnd: 3 19 | bumpmap: 20 | convertToNormalMap: 0 21 | externalNormalMap: 0 22 | heightScale: 0.25 23 | normalMapFilter: 0 24 | isReadable: 0 25 | grayScaleToAlpha: 0 26 | generateCubemap: 6 27 | cubemapConvolution: 0 28 | seamlessCubemap: 0 29 | textureFormat: 1 30 | maxTextureSize: 2048 31 | textureSettings: 32 | serializedVersion: 2 33 | filterMode: -1 34 | aniso: -1 35 | mipBias: -1 36 | wrapU: 1 37 | wrapV: 1 38 | wrapW: 1 39 | nPOTScale: 0 40 | lightmap: 0 41 | compressionQuality: 50 42 | spriteMode: 1 43 | spriteExtrude: 1 44 | spriteMeshType: 1 45 | alignment: 0 46 | spritePivot: {x: 0.5, y: 0.5} 47 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 48 | spritePixelsToUnits: 100 49 | alphaUsage: 1 50 | alphaIsTransparency: 1 51 | spriteTessellationDetail: -1 52 | textureType: 8 53 | textureShape: 1 54 | maxTextureSizeSet: 0 55 | compressionQualitySet: 0 56 | textureFormatSet: 0 57 | platformSettings: 58 | - buildTarget: DefaultTexturePlatform 59 | maxTextureSize: 2048 60 | textureFormat: -1 61 | textureCompression: 1 62 | compressionQuality: 50 63 | crunchedCompression: 0 64 | allowsAlphaSplitting: 0 65 | overridden: 0 66 | spriteSheet: 67 | serializedVersion: 2 68 | sprites: [] 69 | outline: [] 70 | physicsShape: [] 71 | spritePackingTag: 72 | userData: 73 | assetBundleName: 74 | assetBundleVariant: 75 | -------------------------------------------------------------------------------- /fighter_sprites/Resources/fighter_small.psd.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 18810ee94ae0c471d83b931cd069dc75 3 | timeCreated: 1511634178 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 4 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 0 11 | sRGBTexture: 1 12 | linearTexture: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapsPreserveCoverage: 0 16 | alphaTestReferenceValue: 0.5 17 | mipMapFadeDistanceStart: 1 18 | mipMapFadeDistanceEnd: 3 19 | bumpmap: 20 | convertToNormalMap: 0 21 | externalNormalMap: 0 22 | heightScale: 0.25 23 | normalMapFilter: 0 24 | isReadable: 0 25 | grayScaleToAlpha: 0 26 | generateCubemap: 6 27 | cubemapConvolution: 0 28 | seamlessCubemap: 0 29 | textureFormat: 1 30 | maxTextureSize: 2048 31 | textureSettings: 32 | serializedVersion: 2 33 | filterMode: -1 34 | aniso: -1 35 | mipBias: -1 36 | wrapU: 1 37 | wrapV: 1 38 | wrapW: 1 39 | nPOTScale: 0 40 | lightmap: 0 41 | compressionQuality: 50 42 | spriteMode: 1 43 | spriteExtrude: 1 44 | spriteMeshType: 1 45 | alignment: 0 46 | spritePivot: {x: 0.5, y: 0.5} 47 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 48 | spritePixelsToUnits: 100 49 | alphaUsage: 1 50 | alphaIsTransparency: 1 51 | spriteTessellationDetail: -1 52 | textureType: 8 53 | textureShape: 1 54 | maxTextureSizeSet: 0 55 | compressionQualitySet: 0 56 | textureFormatSet: 0 57 | platformSettings: 58 | - buildTarget: DefaultTexturePlatform 59 | maxTextureSize: 2048 60 | textureFormat: -1 61 | textureCompression: 1 62 | compressionQuality: 50 63 | crunchedCompression: 0 64 | allowsAlphaSplitting: 0 65 | overridden: 0 66 | spriteSheet: 67 | serializedVersion: 2 68 | sprites: [] 69 | outline: [] 70 | physicsShape: [] 71 | spritePackingTag: 72 | userData: 73 | assetBundleName: 74 | assetBundleVariant: 75 | -------------------------------------------------------------------------------- /player_model_screenshot.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fe4cdb1d5148742b9aec6336dee6b975 3 | timeCreated: 1515004577 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | externalObjects: {} 8 | serializedVersion: 4 9 | mipmaps: 10 | mipMapMode: 0 11 | enableMipMap: 0 12 | sRGBTexture: 1 13 | linearTexture: 0 14 | fadeOut: 0 15 | borderMipMap: 0 16 | mipMapsPreserveCoverage: 0 17 | alphaTestReferenceValue: 0.5 18 | mipMapFadeDistanceStart: 1 19 | mipMapFadeDistanceEnd: 3 20 | bumpmap: 21 | convertToNormalMap: 0 22 | externalNormalMap: 0 23 | heightScale: 0.25 24 | normalMapFilter: 0 25 | isReadable: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: -1 36 | mipBias: -1 37 | wrapU: 1 38 | wrapV: 1 39 | wrapW: 1 40 | nPOTScale: 0 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 1 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 49 | spritePixelsToUnits: 100 50 | alphaUsage: 1 51 | alphaIsTransparency: 1 52 | spriteTessellationDetail: -1 53 | textureType: 8 54 | textureShape: 1 55 | maxTextureSizeSet: 0 56 | compressionQualitySet: 0 57 | textureFormatSet: 0 58 | platformSettings: 59 | - buildTarget: DefaultTexturePlatform 60 | maxTextureSize: 2048 61 | resizeAlgorithm: 0 62 | textureFormat: -1 63 | textureCompression: 1 64 | compressionQuality: 50 65 | crunchedCompression: 0 66 | allowsAlphaSplitting: 0 67 | overridden: 0 68 | spriteSheet: 69 | serializedVersion: 2 70 | sprites: [] 71 | outline: [] 72 | physicsShape: [] 73 | spritePackingTag: 74 | userData: 75 | assetBundleName: 76 | assetBundleVariant: 77 | -------------------------------------------------------------------------------- /fighter_sprites/Resources/fighter.psd.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7dfed40782fc74fb588b40273aba9b80 3 | timeCreated: 1511204089 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 4 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 1 11 | sRGBTexture: 1 12 | linearTexture: 0 13 | fadeOut: 0 14 | borderMipMap: 1 15 | mipMapsPreserveCoverage: 0 16 | alphaTestReferenceValue: 0.5 17 | mipMapFadeDistanceStart: 1 18 | mipMapFadeDistanceEnd: 3 19 | bumpmap: 20 | convertToNormalMap: 0 21 | externalNormalMap: 0 22 | heightScale: 0.25 23 | normalMapFilter: 0 24 | isReadable: 0 25 | grayScaleToAlpha: 0 26 | generateCubemap: 6 27 | cubemapConvolution: 0 28 | seamlessCubemap: 0 29 | textureFormat: 1 30 | maxTextureSize: 2048 31 | textureSettings: 32 | serializedVersion: 2 33 | filterMode: -1 34 | aniso: 16 35 | mipBias: -1 36 | wrapU: 1 37 | wrapV: 1 38 | wrapW: 1 39 | nPOTScale: 0 40 | lightmap: 0 41 | compressionQuality: 50 42 | spriteMode: 1 43 | spriteExtrude: 1 44 | spriteMeshType: 1 45 | alignment: 0 46 | spritePivot: {x: 0.5, y: 0.5} 47 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 48 | spritePixelsToUnits: 100 49 | alphaUsage: 1 50 | alphaIsTransparency: 1 51 | spriteTessellationDetail: -1 52 | textureType: 8 53 | textureShape: 1 54 | maxTextureSizeSet: 0 55 | compressionQualitySet: 0 56 | textureFormatSet: 0 57 | platformSettings: 58 | - buildTarget: DefaultTexturePlatform 59 | maxTextureSize: 2048 60 | textureFormat: -1 61 | textureCompression: 1 62 | compressionQuality: 50 63 | crunchedCompression: 0 64 | allowsAlphaSplitting: 0 65 | overridden: 0 66 | - buildTarget: Standalone 67 | maxTextureSize: 2048 68 | textureFormat: -1 69 | textureCompression: 1 70 | compressionQuality: 50 71 | crunchedCompression: 0 72 | allowsAlphaSplitting: 0 73 | overridden: 0 74 | spriteSheet: 75 | serializedVersion: 2 76 | sprites: [] 77 | outline: [] 78 | physicsShape: [] 79 | spritePackingTag: 80 | userData: 81 | assetBundleName: 82 | assetBundleVariant: 83 | -------------------------------------------------------------------------------- /fighter_sprites/Resources/missile.psd.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 60d01e1f3bb1d498f9b504fc95ec7a55 3 | timeCreated: 1511636127 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | externalObjects: {} 8 | serializedVersion: 4 9 | mipmaps: 10 | mipMapMode: 0 11 | enableMipMap: 1 12 | sRGBTexture: 1 13 | linearTexture: 0 14 | fadeOut: 0 15 | borderMipMap: 1 16 | mipMapsPreserveCoverage: 0 17 | alphaTestReferenceValue: 0.5 18 | mipMapFadeDistanceStart: 1 19 | mipMapFadeDistanceEnd: 3 20 | bumpmap: 21 | convertToNormalMap: 0 22 | externalNormalMap: 0 23 | heightScale: 0.25 24 | normalMapFilter: 0 25 | isReadable: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: 16 36 | mipBias: -1 37 | wrapU: 1 38 | wrapV: 1 39 | wrapW: 1 40 | nPOTScale: 0 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 1 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 49 | spritePixelsToUnits: 1000 50 | alphaUsage: 1 51 | alphaIsTransparency: 1 52 | spriteTessellationDetail: -1 53 | textureType: 8 54 | textureShape: 1 55 | maxTextureSizeSet: 0 56 | compressionQualitySet: 0 57 | textureFormatSet: 0 58 | platformSettings: 59 | - buildTarget: DefaultTexturePlatform 60 | maxTextureSize: 2048 61 | resizeAlgorithm: 0 62 | textureFormat: -1 63 | textureCompression: 1 64 | compressionQuality: 50 65 | crunchedCompression: 0 66 | allowsAlphaSplitting: 0 67 | overridden: 0 68 | - buildTarget: Standalone 69 | maxTextureSize: 2048 70 | resizeAlgorithm: 0 71 | textureFormat: -1 72 | textureCompression: 1 73 | compressionQuality: 50 74 | crunchedCompression: 0 75 | allowsAlphaSplitting: 0 76 | overridden: 0 77 | spriteSheet: 78 | serializedVersion: 2 79 | sprites: [] 80 | outline: [] 81 | physicsShape: [] 82 | spritePackingTag: 83 | userData: 84 | assetBundleName: 85 | assetBundleVariant: 86 | -------------------------------------------------------------------------------- /fighter_sprites/Resources/villain.psd.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b997321eba64f4905888a793f1e7e3fe 3 | timeCreated: 1511326699 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | externalObjects: {} 8 | serializedVersion: 4 9 | mipmaps: 10 | mipMapMode: 0 11 | enableMipMap: 1 12 | sRGBTexture: 1 13 | linearTexture: 0 14 | fadeOut: 0 15 | borderMipMap: 1 16 | mipMapsPreserveCoverage: 0 17 | alphaTestReferenceValue: 0.5 18 | mipMapFadeDistanceStart: 1 19 | mipMapFadeDistanceEnd: 3 20 | bumpmap: 21 | convertToNormalMap: 0 22 | externalNormalMap: 0 23 | heightScale: 0.25 24 | normalMapFilter: 0 25 | isReadable: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: -1 36 | mipBias: -1 37 | wrapU: 1 38 | wrapV: 1 39 | wrapW: 1 40 | nPOTScale: 0 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 1 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 49 | spritePixelsToUnits: 200 50 | alphaUsage: 1 51 | alphaIsTransparency: 1 52 | spriteTessellationDetail: -1 53 | textureType: 8 54 | textureShape: 1 55 | maxTextureSizeSet: 0 56 | compressionQualitySet: 0 57 | textureFormatSet: 0 58 | platformSettings: 59 | - buildTarget: DefaultTexturePlatform 60 | maxTextureSize: 2048 61 | resizeAlgorithm: 0 62 | textureFormat: -1 63 | textureCompression: 1 64 | compressionQuality: 50 65 | crunchedCompression: 0 66 | allowsAlphaSplitting: 0 67 | overridden: 0 68 | - buildTarget: Standalone 69 | maxTextureSize: 2048 70 | resizeAlgorithm: 0 71 | textureFormat: -1 72 | textureCompression: 1 73 | compressionQuality: 50 74 | crunchedCompression: 0 75 | allowsAlphaSplitting: 0 76 | overridden: 0 77 | spriteSheet: 78 | serializedVersion: 2 79 | sprites: [] 80 | outline: [] 81 | physicsShape: [] 82 | spritePackingTag: 83 | userData: 84 | assetBundleName: 85 | assetBundleVariant: 86 | -------------------------------------------------------------------------------- /prefabs/Resources/missile.prefab: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1001 &100100000 4 | Prefab: 5 | m_ObjectHideFlags: 1 6 | serializedVersion: 2 7 | m_Modification: 8 | m_TransformParent: {fileID: 0} 9 | m_Modifications: [] 10 | m_RemovedComponents: [] 11 | m_ParentPrefab: {fileID: 0} 12 | m_RootGameObject: {fileID: 1078706859849454} 13 | m_IsPrefabParent: 1 14 | --- !u!1 &1078706859849454 15 | GameObject: 16 | m_ObjectHideFlags: 0 17 | m_PrefabParentObject: {fileID: 0} 18 | m_PrefabInternal: {fileID: 100100000} 19 | serializedVersion: 5 20 | m_Component: 21 | - component: {fileID: 4163946525568116} 22 | - component: {fileID: 212266938078572254} 23 | - component: {fileID: 50482246617674784} 24 | - component: {fileID: 58930747941052708} 25 | - component: {fileID: 70262358009982412} 26 | - component: {fileID: 70100309156564654} 27 | m_Layer: 0 28 | m_Name: missile 29 | m_TagString: Untagged 30 | m_Icon: {fileID: 0} 31 | m_NavMeshLayer: 0 32 | m_StaticEditorFlags: 0 33 | m_IsActive: 1 34 | --- !u!4 &4163946525568116 35 | Transform: 36 | m_ObjectHideFlags: 1 37 | m_PrefabParentObject: {fileID: 0} 38 | m_PrefabInternal: {fileID: 100100000} 39 | m_GameObject: {fileID: 1078706859849454} 40 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 41 | m_LocalPosition: {x: 1.478, y: -0.7002257, z: 0} 42 | m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} 43 | m_Children: [] 44 | m_Father: {fileID: 0} 45 | m_RootOrder: 0 46 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 47 | --- !u!50 &50482246617674784 48 | Rigidbody2D: 49 | serializedVersion: 4 50 | m_ObjectHideFlags: 1 51 | m_PrefabParentObject: {fileID: 0} 52 | m_PrefabInternal: {fileID: 100100000} 53 | m_GameObject: {fileID: 1078706859849454} 54 | m_BodyType: 1 55 | m_Simulated: 1 56 | m_UseFullKinematicContacts: 0 57 | m_UseAutoMass: 0 58 | m_Mass: 0.0001 59 | m_LinearDrag: 0 60 | m_AngularDrag: 0.05 61 | m_GravityScale: 0 62 | m_Material: {fileID: 0} 63 | m_Interpolate: 0 64 | m_SleepingMode: 1 65 | m_CollisionDetection: 0 66 | m_Constraints: 0 67 | --- !u!58 &58930747941052708 68 | CircleCollider2D: 69 | m_ObjectHideFlags: 1 70 | m_PrefabParentObject: {fileID: 0} 71 | m_PrefabInternal: {fileID: 100100000} 72 | m_GameObject: {fileID: 1078706859849454} 73 | m_Enabled: 1 74 | m_Density: 1 75 | m_Material: {fileID: 0} 76 | m_IsTrigger: 1 77 | m_UsedByEffector: 0 78 | m_UsedByComposite: 0 79 | m_Offset: {x: 0.84, y: 0} 80 | serializedVersion: 2 81 | m_Radius: 0.06 82 | --- !u!70 &70100309156564654 83 | CapsuleCollider2D: 84 | m_ObjectHideFlags: 1 85 | m_PrefabParentObject: {fileID: 0} 86 | m_PrefabInternal: {fileID: 100100000} 87 | m_GameObject: {fileID: 1078706859849454} 88 | m_Enabled: 1 89 | m_Density: 1 90 | m_Material: {fileID: 0} 91 | m_IsTrigger: 1 92 | m_UsedByEffector: 0 93 | m_UsedByComposite: 0 94 | m_Offset: {x: 0.04, y: 0} 95 | m_Size: {x: 0.96, y: 0.35} 96 | m_Direction: 1 97 | --- !u!70 &70262358009982412 98 | CapsuleCollider2D: 99 | m_ObjectHideFlags: 1 100 | m_PrefabParentObject: {fileID: 0} 101 | m_PrefabInternal: {fileID: 100100000} 102 | m_GameObject: {fileID: 1078706859849454} 103 | m_Enabled: 1 104 | m_Density: 1 105 | m_Material: {fileID: 0} 106 | m_IsTrigger: 1 107 | m_UsedByEffector: 0 108 | m_UsedByComposite: 0 109 | m_Offset: {x: -0.01, y: 0} 110 | m_Size: {x: 1.65, y: 0.24} 111 | m_Direction: 1 112 | --- !u!212 &212266938078572254 113 | SpriteRenderer: 114 | m_ObjectHideFlags: 1 115 | m_PrefabParentObject: {fileID: 0} 116 | m_PrefabInternal: {fileID: 100100000} 117 | m_GameObject: {fileID: 1078706859849454} 118 | m_Enabled: 1 119 | m_CastShadows: 0 120 | m_ReceiveShadows: 0 121 | m_DynamicOccludee: 1 122 | m_MotionVectors: 1 123 | m_LightProbeUsage: 1 124 | m_ReflectionProbeUsage: 1 125 | m_Materials: 126 | - {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0} 127 | m_StaticBatchInfo: 128 | firstSubMesh: 0 129 | subMeshCount: 0 130 | m_StaticBatchRoot: {fileID: 0} 131 | m_ProbeAnchor: {fileID: 0} 132 | m_LightProbeVolumeOverride: {fileID: 0} 133 | m_ScaleInLightmap: 1 134 | m_PreserveUVs: 0 135 | m_IgnoreNormalsForChartDetection: 0 136 | m_ImportantGI: 0 137 | m_StitchLightmapSeams: 0 138 | m_SelectedEditorRenderState: 0 139 | m_MinimumChartSize: 4 140 | m_AutoUVMaxDistance: 0.5 141 | m_AutoUVMaxAngle: 89 142 | m_LightmapParameters: {fileID: 0} 143 | m_SortingLayerID: 0 144 | m_SortingLayer: 0 145 | m_SortingOrder: 0 146 | m_Sprite: {fileID: 21300000, guid: 60d01e1f3bb1d498f9b504fc95ec7a55, type: 3} 147 | m_Color: {r: 1, g: 1, b: 1, a: 1} 148 | m_FlipX: 0 149 | m_FlipY: 0 150 | m_DrawMode: 0 151 | m_Size: {x: 17.97, y: 3.82} 152 | m_AdaptiveModeThreshold: 0.5 153 | m_SpriteTileMode: 0 154 | m_WasSpriteAssigned: 1 155 | m_MaskInteraction: 0 156 | -------------------------------------------------------------------------------- /prefabs/Resources/villain.prefab: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1001 &100100000 4 | Prefab: 5 | m_ObjectHideFlags: 1 6 | serializedVersion: 2 7 | m_Modification: 8 | m_TransformParent: {fileID: 0} 9 | m_Modifications: [] 10 | m_RemovedComponents: [] 11 | m_ParentPrefab: {fileID: 0} 12 | m_RootGameObject: {fileID: 1842417603740258} 13 | m_IsPrefabParent: 1 14 | --- !u!1 &1842417603740258 15 | GameObject: 16 | m_ObjectHideFlags: 0 17 | m_PrefabParentObject: {fileID: 0} 18 | m_PrefabInternal: {fileID: 100100000} 19 | serializedVersion: 5 20 | m_Component: 21 | - component: {fileID: 4798875279180966} 22 | - component: {fileID: 212753900898678892} 23 | - component: {fileID: 50291106030312376} 24 | - component: {fileID: 70257487093870496} 25 | - component: {fileID: 58412864009655150} 26 | - component: {fileID: 58087728240805942} 27 | - component: {fileID: 58695909204995370} 28 | - component: {fileID: 58758809837456078} 29 | - component: {fileID: 58576441452708816} 30 | - component: {fileID: 58021972737298574} 31 | m_Layer: 11 32 | m_Name: villain 33 | m_TagString: Untagged 34 | m_Icon: {fileID: 0} 35 | m_NavMeshLayer: 0 36 | m_StaticEditorFlags: 0 37 | m_IsActive: 1 38 | --- !u!4 &4798875279180966 39 | Transform: 40 | m_ObjectHideFlags: 1 41 | m_PrefabParentObject: {fileID: 0} 42 | m_PrefabInternal: {fileID: 100100000} 43 | m_GameObject: {fileID: 1842417603740258} 44 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 45 | m_LocalPosition: {x: 0, y: -0.89613074, z: 0} 46 | m_LocalScale: {x: 1, y: 1, z: 1} 47 | m_Children: [] 48 | m_Father: {fileID: 0} 49 | m_RootOrder: 0 50 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 51 | --- !u!50 &50291106030312376 52 | Rigidbody2D: 53 | serializedVersion: 4 54 | m_ObjectHideFlags: 1 55 | m_PrefabParentObject: {fileID: 0} 56 | m_PrefabInternal: {fileID: 100100000} 57 | m_GameObject: {fileID: 1842417603740258} 58 | m_BodyType: 0 59 | m_Simulated: 1 60 | m_UseFullKinematicContacts: 0 61 | m_UseAutoMass: 0 62 | m_Mass: 1 63 | m_LinearDrag: 0 64 | m_AngularDrag: 0.05 65 | m_GravityScale: 0 66 | m_Material: {fileID: 0} 67 | m_Interpolate: 0 68 | m_SleepingMode: 1 69 | m_CollisionDetection: 0 70 | m_Constraints: 0 71 | --- !u!58 &58021972737298574 72 | CircleCollider2D: 73 | m_ObjectHideFlags: 1 74 | m_PrefabParentObject: {fileID: 0} 75 | m_PrefabInternal: {fileID: 100100000} 76 | m_GameObject: {fileID: 1842417603740258} 77 | m_Enabled: 1 78 | m_Density: 1 79 | m_Material: {fileID: 0} 80 | m_IsTrigger: 0 81 | m_UsedByEffector: 0 82 | m_UsedByComposite: 0 83 | m_Offset: {x: -1.35, y: -1.65} 84 | serializedVersion: 2 85 | m_Radius: 1.27 86 | --- !u!58 &58087728240805942 87 | CircleCollider2D: 88 | m_ObjectHideFlags: 1 89 | m_PrefabParentObject: {fileID: 0} 90 | m_PrefabInternal: {fileID: 100100000} 91 | m_GameObject: {fileID: 1842417603740258} 92 | m_Enabled: 1 93 | m_Density: 1 94 | m_Material: {fileID: 0} 95 | m_IsTrigger: 1 96 | m_UsedByEffector: 0 97 | m_UsedByComposite: 0 98 | m_Offset: {x: 1.58, y: 1.61} 99 | serializedVersion: 2 100 | m_Radius: 0.85 101 | --- !u!58 &58412864009655150 102 | CircleCollider2D: 103 | m_ObjectHideFlags: 1 104 | m_PrefabParentObject: {fileID: 0} 105 | m_PrefabInternal: {fileID: 100100000} 106 | m_GameObject: {fileID: 1842417603740258} 107 | m_Enabled: 1 108 | m_Density: 1 109 | m_Material: {fileID: 0} 110 | m_IsTrigger: 0 111 | m_UsedByEffector: 0 112 | m_UsedByComposite: 0 113 | m_Offset: {x: -1.35, y: 1.65} 114 | serializedVersion: 2 115 | m_Radius: 1.27 116 | --- !u!58 &58576441452708816 117 | CircleCollider2D: 118 | m_ObjectHideFlags: 1 119 | m_PrefabParentObject: {fileID: 0} 120 | m_PrefabInternal: {fileID: 100100000} 121 | m_GameObject: {fileID: 1842417603740258} 122 | m_Enabled: 1 123 | m_Density: 1 124 | m_Material: {fileID: 0} 125 | m_IsTrigger: 0 126 | m_UsedByEffector: 0 127 | m_UsedByComposite: 0 128 | m_Offset: {x: 1.58, y: -1.61} 129 | serializedVersion: 2 130 | m_Radius: 0.85 131 | --- !u!58 &58695909204995370 132 | CircleCollider2D: 133 | m_ObjectHideFlags: 1 134 | m_PrefabParentObject: {fileID: 0} 135 | m_PrefabInternal: {fileID: 100100000} 136 | m_GameObject: {fileID: 1842417603740258} 137 | m_Enabled: 1 138 | m_Density: 1 139 | m_Material: {fileID: 0} 140 | m_IsTrigger: 0 141 | m_UsedByEffector: 0 142 | m_UsedByComposite: 0 143 | m_Offset: {x: 1.34, y: 2.73} 144 | serializedVersion: 2 145 | m_Radius: 0.39 146 | --- !u!58 &58758809837456078 147 | CircleCollider2D: 148 | m_ObjectHideFlags: 1 149 | m_PrefabParentObject: {fileID: 0} 150 | m_PrefabInternal: {fileID: 100100000} 151 | m_GameObject: {fileID: 1842417603740258} 152 | m_Enabled: 1 153 | m_Density: 1 154 | m_Material: {fileID: 0} 155 | m_IsTrigger: 0 156 | m_UsedByEffector: 0 157 | m_UsedByComposite: 0 158 | m_Offset: {x: 1.34, y: -2.73} 159 | serializedVersion: 2 160 | m_Radius: 0.39 161 | --- !u!70 &70257487093870496 162 | CapsuleCollider2D: 163 | m_ObjectHideFlags: 1 164 | m_PrefabParentObject: {fileID: 0} 165 | m_PrefabInternal: {fileID: 100100000} 166 | m_GameObject: {fileID: 1842417603740258} 167 | m_Enabled: 1 168 | m_Density: 1 169 | m_Material: {fileID: 0} 170 | m_IsTrigger: 0 171 | m_UsedByEffector: 0 172 | m_UsedByComposite: 0 173 | m_Offset: {x: -0.08, y: 0} 174 | m_Size: {x: 2.17, y: 7.21} 175 | m_Direction: 0 176 | --- !u!212 &212753900898678892 177 | SpriteRenderer: 178 | m_ObjectHideFlags: 1 179 | m_PrefabParentObject: {fileID: 0} 180 | m_PrefabInternal: {fileID: 100100000} 181 | m_GameObject: {fileID: 1842417603740258} 182 | m_Enabled: 1 183 | m_CastShadows: 0 184 | m_ReceiveShadows: 0 185 | m_DynamicOccludee: 1 186 | m_MotionVectors: 1 187 | m_LightProbeUsage: 1 188 | m_ReflectionProbeUsage: 1 189 | m_Materials: 190 | - {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0} 191 | m_StaticBatchInfo: 192 | firstSubMesh: 0 193 | subMeshCount: 0 194 | m_StaticBatchRoot: {fileID: 0} 195 | m_ProbeAnchor: {fileID: 0} 196 | m_LightProbeVolumeOverride: {fileID: 0} 197 | m_ScaleInLightmap: 1 198 | m_PreserveUVs: 0 199 | m_IgnoreNormalsForChartDetection: 0 200 | m_ImportantGI: 0 201 | m_StitchLightmapSeams: 0 202 | m_SelectedEditorRenderState: 0 203 | m_MinimumChartSize: 4 204 | m_AutoUVMaxDistance: 0.5 205 | m_AutoUVMaxAngle: 89 206 | m_LightmapParameters: {fileID: 0} 207 | m_SortingLayerID: 0 208 | m_SortingLayer: 0 209 | m_SortingOrder: 0 210 | m_Sprite: {fileID: 21300000, guid: b997321eba64f4905888a793f1e7e3fe, type: 3} 211 | m_Color: {r: 1, g: 1, b: 1, a: 1} 212 | m_FlipX: 0 213 | m_FlipY: 0 214 | m_DrawMode: 0 215 | m_Size: {x: 10.72, y: 14.42} 216 | m_AdaptiveModeThreshold: 0.5 217 | m_SpriteTileMode: 0 218 | m_WasSpriteAssigned: 1 219 | m_MaskInteraction: 0 220 | -------------------------------------------------------------------------------- /fighter_tutorial/core.clj: -------------------------------------------------------------------------------- 1 | (ns fighter-tutorial.core 2 | (:use arcadia.core arcadia.linear) 3 | (:require [arcadia.sugar :as a] ; For augmented destructuring and imperative code 4 | [arcadia.scene :as scn]) ; For keeping track of stuff we put in the scene 5 | (:import [UnityEngine Collider2D Physics ; Heavy interop... 6 | GameObject Input Rigidbody2D 7 | Vector2 Mathf Resources Transform 8 | Collision2D Physics2D] 9 | ArcadiaState)) ; Handles our state 10 | 11 | ;; The following is completed code for the tutorial. 12 | ;; You can follow along by either uncommenting chunks of code as the 13 | ;; tutorial gets to them, or writing your own in step with the 14 | ;; tutorial. To see the full game, uncomment everything below and 15 | ;; run (setup) from the repl. 16 | 17 | (defn bearing-vector [angle] 18 | (let [angle (* Mathf/Deg2Rad angle)] 19 | (v2 (Mathf/Cos angle) (Mathf/Sin angle)))) 20 | 21 | (defn abs-angle [v] 22 | (* Mathf/Rad2Deg 23 | (Mathf/Atan2 (.y v) (.x v)))) 24 | 25 | (defn controller-vector [] 26 | (v2 (Input/GetAxis "Horizontal") 27 | (Input/GetAxis "Vertical"))) 28 | 29 | (defn wasd-key [] 30 | (or (Input/GetKey "w") 31 | (Input/GetKey "a") 32 | (Input/GetKey "s") 33 | (Input/GetKey "d"))) 34 | 35 | (defn do-ignore-collisions [^Collider2D col1, ^Collider2D col2] 36 | (Physics2D/IgnoreCollision col1 col2 true) 37 | (Physics2D/IgnoreCollision col2 col1 true)) 38 | 39 | (defn move-forward [^Rigidbody2D rb, distance] 40 | (.MovePosition rb 41 | (v2+ (.position rb) 42 | (v2* (bearing-vector (.rotation rb)) 43 | distance)))) 44 | 45 | ;; ============================================================ 46 | ;; health 47 | 48 | ;; health, remove from scene when zero 49 | ;; expects to be keyed at ::health 50 | (defrole health-role 51 | :state {:health 1} 52 | (update [obj k] 53 | (let [{:keys [health]} (state obj k)] 54 | (when (<= health 0) 55 | (retire obj))))) 56 | 57 | (defn damage [obj amt] 58 | (update-state obj ::health update :health - amt)) 59 | 60 | ;; ============================================================ 61 | ;; layers 62 | 63 | (def player-bullets-layer (UnityEngine.LayerMask/NameToLayer "player-bullets")) 64 | 65 | (def enemy-bullets-layer (UnityEngine.LayerMask/NameToLayer "enemy-bullets")) 66 | 67 | ;; ============================================================ 68 | ;; bullet 69 | 70 | ;; ------------------------------------------------------------ 71 | ;; bullet collision 72 | 73 | (defrole bullet-collision 74 | (on-trigger-enter2d [bullet, ^Collider2D collider, k] 75 | (let [obj2 (.gameObject collider)] 76 | (when (state obj2 ::health) 77 | (damage obj2 1) 78 | (retire bullet))))) 79 | 80 | ;; ------------------------------------------------------------ 81 | ;; bullet lifespans 82 | 83 | (defrole lifespan-role 84 | :state {:start System.DateTime/Now 85 | :lifespan 0} 86 | (update [obj k] 87 | (let [{:keys [start lifespan]} (state obj k)] 88 | (when (< lifespan (.TotalMilliseconds (.Subtract System.DateTime/Now start))) 89 | (retire obj))))) 90 | 91 | ;; ------------------------------------------------------------ 92 | ;; bullet movement 93 | 94 | (defrole bullet-movement-role 95 | (fixed-update [bullet k] 96 | (with-cmpt bullet [rb Rigidbody2D] 97 | (move-forward rb 0.2)))) 98 | 99 | ;; ------------------------------------------------------------ 100 | ;; bullet roles 101 | 102 | (def bullet-roles 103 | {::movement bullet-movement-role 104 | ::lifespan lifespan-role 105 | ::collision bullet-collision}) 106 | 107 | ;; ------------------------------------------------------------ 108 | ;; shooting 109 | 110 | (defn shoot-bullet [start bearing] 111 | (let [bullet (GameObject/Instantiate 112 | (Resources/Load "missile" GameObject))] 113 | (with-cmpt bullet [rb Rigidbody2D, 114 | tr Transform] 115 | (scn/register bullet ::bullet) 116 | (set! (.position tr) (v3 (.x start) (.y start) 1)) 117 | (.MoveRotation rb bearing) 118 | (roles+ bullet 119 | (-> bullet-roles 120 | (assoc-in [::lifespan :state :start] System.DateTime/Now) 121 | (assoc-in [::lifespan :state :lifespan] 2000))) 122 | bullet))) 123 | 124 | (defn shoot [obj layer] 125 | (with-cmpt obj [rb Rigidbody2D] 126 | (let [bullet (shoot-bullet (.position rb) (.rotation rb))] 127 | (set! (.layer bullet) layer) 128 | bullet))) 129 | 130 | ;; ============================================================ 131 | ;; player 132 | 133 | ;; ------------------------------------------------------------ 134 | ;; player movement 135 | 136 | (defn player-movement-fixed-update [obj k] 137 | (with-cmpt obj [rb Rigidbody2D] 138 | (when (wasd-key) 139 | (.MoveRotation rb (abs-angle (controller-vector))) 140 | (set! (.angularVelocity rb) 0) 141 | (.AddForce rb 142 | (v2* (bearing-vector (.rotation rb)) 143 | 3))))) 144 | 145 | (def player-movement-role 146 | {:fixed-update #'player-movement-fixed-update}) 147 | 148 | ;; ------------------------------------------------------------ 149 | ;; player shooting 150 | 151 | (defrole player-shooting-role 152 | (update [obj k] 153 | (with-cmpt obj [rb Rigidbody2D] 154 | (when (Input/GetKeyDown "space") 155 | (shoot obj player-bullets-layer))))) 156 | 157 | ;; ------------------------------------------------------------ 158 | ;; player roles 159 | 160 | (def player-roles 161 | {::shooting player-shooting-role 162 | ::movement player-movement-role 163 | ::health (update health-role :state assoc :health 10)}) 164 | 165 | ;; ============================================================ 166 | ;; enemy 167 | 168 | ;; ------------------------------------------------------------ 169 | ;; enemy shooting 170 | 171 | (defrole enemy-shooting-role 172 | :state {:last-shot System.DateTime/Now} 173 | (update [obj k] 174 | (let [{:keys [target last-shot]} (state obj k) 175 | now System.DateTime/Now] 176 | (when (and (obj-nil target) ; check that the target is neither nil nor a null object 177 | (< 1000 (.TotalMilliseconds (.Subtract now last-shot)))) 178 | (update-state obj k assoc :last-shot now) 179 | (shoot obj enemy-bullets-layer))))) 180 | 181 | ;; ------------------------------------------------------------ 182 | ;; enemy movement 183 | 184 | (defrole enemy-movement-role 185 | :state {:target nil} 186 | (fixed-update [obj k] 187 | ;; Make sure the target is neither nil nor the null object using 188 | ;; `obj-nil` 189 | (when-let [target (obj-nil (:target (state obj k)))] 190 | ;; We're going to use augmented destructuring to access 191 | ;; components of GameObjects using `arcadia.sugar/with-cmpt`, 192 | ;; and access object properties and fields using 193 | ;; `arcadia.sugar/o`. See the docs for `arcadia.sugar/let` for 194 | ;; more details. 195 | (a/let [(a/with-cmpt rb1 Rigidbody2D) obj ; Get the Rigidbody2D component 196 | (a/o pos1 position, rot1 rotation) rb1 ; Get the position and rotation from it 197 | (a/with-cmpt (a/o pos2 position) Rigidbody2D) target ; Get the position of the target's Rigidbody2D 198 | pos-diff (v2- pos2 pos1) ; Get the difference vector from the object to the target 199 | rot-diff (Vector2/SignedAngle ; Get the rotation needed to face the target 200 | (bearing-vector rot1) 201 | pos-diff)] 202 | (.MoveRotation rb1 ; rotate the Rigidbody2D towards the target, clamped to one degree per frame 203 | (+ rot1 (Mathf/Clamp -1 rot-diff 1))))))) 204 | 205 | ;; ------------------------------------------------------------ 206 | ;; enemy roles 207 | 208 | (def enemy-roles 209 | {::shooting enemy-shooting-role 210 | ::movement enemy-movement-role 211 | ::health (update health-role :state assoc :health 10)}) 212 | 213 | (defn make-enemy [protagonist] 214 | (let [enemy (GameObject/Instantiate (Resources/Load "villain" GameObject))] 215 | (scn/register enemy ::enemy) 216 | (roles+ enemy 217 | (-> enemy-roles 218 | (assoc-in [::movement :state :target] protagonist) 219 | (assoc-in [::shooting :state :target] protagonist))))) 220 | 221 | ;; ============================================================ 222 | ;; setup 223 | 224 | (defn setup [] 225 | (scn/retire ::enemy ::bullet ::player) 226 | (let [player (GameObject/Instantiate (Resources/Load "fighter"))] 227 | (scn/register player ::player) 228 | (set! (.name player) "player") 229 | (roles+ player player-roles) 230 | (make-enemy player))) 231 | 232 | -------------------------------------------------------------------------------- /prefabs/Resources/fighter.prefab: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1001 &100100000 4 | Prefab: 5 | m_ObjectHideFlags: 1 6 | serializedVersion: 2 7 | m_Modification: 8 | m_TransformParent: {fileID: 0} 9 | m_Modifications: [] 10 | m_RemovedComponents: [] 11 | m_ParentPrefab: {fileID: 0} 12 | m_RootGameObject: {fileID: 1572561591838158} 13 | m_IsPrefabParent: 1 14 | --- !u!1 &1572561591838158 15 | GameObject: 16 | m_ObjectHideFlags: 0 17 | m_PrefabParentObject: {fileID: 0} 18 | m_PrefabInternal: {fileID: 100100000} 19 | serializedVersion: 5 20 | m_Component: 21 | - component: {fileID: 4734308723596882} 22 | - component: {fileID: 212073141053345930} 23 | - component: {fileID: 60797936089985858} 24 | - component: {fileID: 50220606914978352} 25 | - component: {fileID: 70392683732393636} 26 | - component: {fileID: 70622904315723244} 27 | - component: {fileID: 70677061617428146} 28 | - component: {fileID: 70918757404388904} 29 | - component: {fileID: 70827430596756724} 30 | - component: {fileID: 70063027464566348} 31 | - component: {fileID: 70332620859823278} 32 | - component: {fileID: 70502415214213586} 33 | m_Layer: 10 34 | m_Name: fighter 35 | m_TagString: Untagged 36 | m_Icon: {fileID: 0} 37 | m_NavMeshLayer: 0 38 | m_StaticEditorFlags: 0 39 | m_IsActive: 1 40 | --- !u!4 &4734308723596882 41 | Transform: 42 | m_ObjectHideFlags: 1 43 | m_PrefabParentObject: {fileID: 0} 44 | m_PrefabInternal: {fileID: 100100000} 45 | m_GameObject: {fileID: 1572561591838158} 46 | m_LocalRotation: {x: 0, y: -0, z: -0.0025605953, w: 0.9999968} 47 | m_LocalPosition: {x: 6.5256643, y: -0.48319912, z: 0} 48 | m_LocalScale: {x: 0.2, y: 0.2, z: 0.2} 49 | m_Children: [] 50 | m_Father: {fileID: 0} 51 | m_RootOrder: 0 52 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: -0.293} 53 | --- !u!50 &50220606914978352 54 | Rigidbody2D: 55 | serializedVersion: 4 56 | m_ObjectHideFlags: 1 57 | m_PrefabParentObject: {fileID: 0} 58 | m_PrefabInternal: {fileID: 100100000} 59 | m_GameObject: {fileID: 1572561591838158} 60 | m_BodyType: 0 61 | m_Simulated: 1 62 | m_UseFullKinematicContacts: 0 63 | m_UseAutoMass: 0 64 | m_Mass: 1 65 | m_LinearDrag: 0 66 | m_AngularDrag: 0.05 67 | m_GravityScale: 0 68 | m_Material: {fileID: 0} 69 | m_Interpolate: 0 70 | m_SleepingMode: 1 71 | m_CollisionDetection: 0 72 | m_Constraints: 0 73 | --- !u!60 &60797936089985858 74 | PolygonCollider2D: 75 | m_ObjectHideFlags: 1 76 | m_PrefabParentObject: {fileID: 0} 77 | m_PrefabInternal: {fileID: 100100000} 78 | m_GameObject: {fileID: 1572561591838158} 79 | m_Enabled: 0 80 | m_Density: 1 81 | m_Material: {fileID: 0} 82 | m_IsTrigger: 0 83 | m_UsedByEffector: 0 84 | m_UsedByComposite: 0 85 | m_Offset: {x: 0, y: 0} 86 | m_SpriteTilingProperty: 87 | border: {x: 0, y: 0, z: 0, w: 0} 88 | pivot: {x: 0.5, y: 0.5} 89 | oldSize: {x: 3.24, y: 4.94} 90 | newSize: {x: 4.83, y: 4.84} 91 | adaptiveTilingThreshold: 0.5 92 | drawMode: 0 93 | adaptiveTiling: 0 94 | m_AutoTiling: 0 95 | m_Points: 96 | m_Paths: 97 | - - {x: -0.099999994, y: -1.5699999} 98 | - {x: -0.13, y: -1.37} 99 | - {x: -0.14999999, y: -1.1899999} 100 | - {x: -0.17999999, y: -1.0799999} 101 | - {x: -0.24, y: -0.76} 102 | - {x: -0.24, y: -0.48999998} 103 | - {x: -0.22999999, y: -0.35} 104 | - {x: -0.19, y: -0.29} 105 | - {x: -0.14, y: -0.19} 106 | - {x: -0.089999996, y: -0.13} 107 | - {x: 0.049999997, y: -0.08} 108 | - {x: 0.29999998, y: -0.04} 109 | - {x: 0.42999998, y: -0.04} 110 | - {x: 0.66999996, y: -0.049999997} 111 | - {x: 0.79999995, y: -0.08} 112 | - {x: 0.98999995, y: -0.07} 113 | - {x: 1.0699999, y: -0.02} 114 | - {x: 1.16, y: -0.03} 115 | - {x: 1.23, y: 0.03} 116 | - {x: 1.23, y: 0.06} 117 | - {x: 1.17, y: 0.11} 118 | - {x: 1, y: 0.13} 119 | - {x: 0.91999996, y: 0.16} 120 | - {x: 0.76, y: 0.14999999} 121 | - {x: 0.61, y: 0.12} 122 | - {x: 0.35999998, y: 0.12} 123 | - {x: 0.16, y: 0.13} 124 | - {x: -0.02, y: 0.17999999} 125 | - {x: -0.12, y: 0.25} 126 | - {x: -0.19, y: 0.34} 127 | - {x: -0.24, y: 0.53999996} 128 | - {x: -0.24, y: 0.76} 129 | - {x: -0.21, y: 0.93} 130 | - {x: -0.12, y: 1.55} 131 | - {x: -0.089999996, y: 1.5999999} 132 | - {x: -0.089999996, y: 1.86} 133 | - {x: -0.099999994, y: 2.03} 134 | - {x: -0.12, y: 2.1399999} 135 | - {x: -0.17, y: 2.32} 136 | - {x: -0.21, y: 2.35} 137 | - {x: -0.26, y: 2.35} 138 | - {x: -0.32999998, y: 2.19} 139 | - {x: -0.41, y: 2.1499999} 140 | - {x: -0.42, y: 2.08} 141 | - {x: -0.5, y: 2.01} 142 | - {x: -0.55, y: 1.9699999} 143 | - {x: -0.59, y: 1.9} 144 | - {x: -0.59999996, y: 1.8199999} 145 | - {x: -0.65999997, y: 1.75} 146 | - {x: -0.66999996, y: 1.65} 147 | - {x: -0.71, y: 1.66} 148 | - {x: -0.74, y: 1.61} 149 | - {x: -0.72999996, y: 1.56} 150 | - {x: -0.71999997, y: 1.5} 151 | - {x: -0.78, y: 1.4599999} 152 | - {x: -0.78999996, y: 1.36} 153 | - {x: -0.78, y: 1.01} 154 | - {x: -0.81, y: 0.98999995} 155 | - {x: -0.83, y: 0.87} 156 | - {x: -0.84, y: 0.65999997} 157 | - {x: -0.83, y: 0.44} 158 | - {x: -0.84999996, y: 0.38} 159 | - {x: -0.84, y: 0.26999998} 160 | - {x: -0.9, y: 0.22999999} 161 | - {x: -1.04, y: 0.25} 162 | - {x: -1.13, y: 0.29999998} 163 | - {x: -1.25, y: 0.42999998} 164 | - {x: -1.3399999, y: 0.42} 165 | - {x: -1.39, y: 0.39} 166 | - {x: -1.49, y: 0.28} 167 | - {x: -1.53, y: 0.14} 168 | - {x: -1.53, y: -0.07} 169 | - {x: -1.49, y: -0.19999999} 170 | - {x: -1.41, y: -0.29} 171 | - {x: -1.3399999, y: -0.34} 172 | - {x: -1.24, y: -0.35} 173 | - {x: -1.16, y: -0.25} 174 | - {x: -0.90999997, y: -0.14} 175 | - {x: -0.84, y: -0.19} 176 | - {x: -0.84999996, y: -0.26} 177 | - {x: -0.84, y: -0.48999998} 178 | - {x: -0.84, y: -0.62} 179 | - {x: -0.81, y: -0.72999996} 180 | - {x: -0.83, y: -0.78999996} 181 | - {x: -0.78999996, y: -1.03} 182 | - {x: -0.78999996, y: -1.13} 183 | - {x: -0.77, y: -1.39} 184 | - {x: -0.71999997, y: -1.42} 185 | - {x: -0.71999997, y: -1.55} 186 | - {x: -0.7, y: -1.5699999} 187 | - {x: -0.65, y: -1.5699999} 188 | - {x: -0.65, y: -1.67} 189 | - {x: -0.59, y: -1.73} 190 | - {x: -0.58, y: -1.8499999} 191 | - {x: -0.56, y: -1.89} 192 | - {x: -0.48, y: -1.9599999} 193 | - {x: -0.42, y: -1.9499999} 194 | - {x: -0.42, y: -2.04} 195 | - {x: -0.38, y: -2.11} 196 | - {x: -0.32, y: -2.1399999} 197 | - {x: -0.25, y: -2.27} 198 | - {x: -0.21, y: -2.27} 199 | - {x: -0.17, y: -2.25} 200 | - {x: -0.12, y: -2.1} 201 | - {x: -0.099999994, y: -1.74} 202 | --- !u!70 &70063027464566348 203 | CapsuleCollider2D: 204 | m_ObjectHideFlags: 1 205 | m_PrefabParentObject: {fileID: 0} 206 | m_PrefabInternal: {fileID: 100100000} 207 | m_GameObject: {fileID: 1572561591838158} 208 | m_Enabled: 1 209 | m_Density: 1 210 | m_Material: {fileID: 0} 211 | m_IsTrigger: 0 212 | m_UsedByEffector: 0 213 | m_UsedByComposite: 0 214 | m_Offset: {x: -0.37, y: -1.74} 215 | m_Size: {x: 0.42, y: 0.83} 216 | m_Direction: 0 217 | --- !u!70 &70332620859823278 218 | CapsuleCollider2D: 219 | m_ObjectHideFlags: 1 220 | m_PrefabParentObject: {fileID: 0} 221 | m_PrefabInternal: {fileID: 100100000} 222 | m_GameObject: {fileID: 1572561591838158} 223 | m_Enabled: 1 224 | m_Density: 1 225 | m_Material: {fileID: 0} 226 | m_IsTrigger: 0 227 | m_UsedByEffector: 0 228 | m_UsedByComposite: 0 229 | m_Offset: {x: -0.79, y: 0} 230 | m_Size: {x: 1.47, y: 0.74} 231 | m_Direction: 1 232 | --- !u!70 &70392683732393636 233 | CapsuleCollider2D: 234 | m_ObjectHideFlags: 1 235 | m_PrefabParentObject: {fileID: 0} 236 | m_PrefabInternal: {fileID: 100100000} 237 | m_GameObject: {fileID: 1572561591838158} 238 | m_Enabled: 1 239 | m_Density: 1 240 | m_Material: {fileID: 0} 241 | m_IsTrigger: 0 242 | m_UsedByEffector: 0 243 | m_UsedByComposite: 0 244 | m_Offset: {x: -0.52, y: 0.78} 245 | m_Size: {x: 0.56, y: 1.27} 246 | m_Direction: 0 247 | --- !u!70 &70502415214213586 248 | CapsuleCollider2D: 249 | m_ObjectHideFlags: 1 250 | m_PrefabParentObject: {fileID: 0} 251 | m_PrefabInternal: {fileID: 100100000} 252 | m_GameObject: {fileID: 1572561591838158} 253 | m_Enabled: 1 254 | m_Density: 1 255 | m_Material: {fileID: 0} 256 | m_IsTrigger: 0 257 | m_UsedByEffector: 0 258 | m_UsedByComposite: 0 259 | m_Offset: {x: 0.4, y: 0.04} 260 | m_Size: {x: 1.63, y: 0.26} 261 | m_Direction: 1 262 | --- !u!70 &70622904315723244 263 | CapsuleCollider2D: 264 | m_ObjectHideFlags: 1 265 | m_PrefabParentObject: {fileID: 0} 266 | m_PrefabInternal: {fileID: 100100000} 267 | m_GameObject: {fileID: 1572561591838158} 268 | m_Enabled: 1 269 | m_Density: 1 270 | m_Material: {fileID: 0} 271 | m_IsTrigger: 0 272 | m_UsedByEffector: 0 273 | m_UsedByComposite: 0 274 | m_Offset: {x: -0.37, y: 1.74} 275 | m_Size: {x: 0.42, y: 0.83} 276 | m_Direction: 0 277 | --- !u!70 &70677061617428146 278 | CapsuleCollider2D: 279 | m_ObjectHideFlags: 1 280 | m_PrefabParentObject: {fileID: 0} 281 | m_PrefabInternal: {fileID: 100100000} 282 | m_GameObject: {fileID: 1572561591838158} 283 | m_Enabled: 1 284 | m_Density: 1 285 | m_Material: {fileID: 0} 286 | m_IsTrigger: 0 287 | m_UsedByEffector: 0 288 | m_UsedByComposite: 0 289 | m_Offset: {x: -0.22, y: 1.97} 290 | m_Size: {x: 0.08, y: 0.74} 291 | m_Direction: 0 292 | --- !u!70 &70827430596756724 293 | CapsuleCollider2D: 294 | m_ObjectHideFlags: 1 295 | m_PrefabParentObject: {fileID: 0} 296 | m_PrefabInternal: {fileID: 100100000} 297 | m_GameObject: {fileID: 1572561591838158} 298 | m_Enabled: 1 299 | m_Density: 1 300 | m_Material: {fileID: 0} 301 | m_IsTrigger: 0 302 | m_UsedByEffector: 0 303 | m_UsedByComposite: 0 304 | m_Offset: {x: -0.22, y: -1.97} 305 | m_Size: {x: 0.08, y: 0.7} 306 | m_Direction: 0 307 | --- !u!70 &70918757404388904 308 | CapsuleCollider2D: 309 | m_ObjectHideFlags: 1 310 | m_PrefabParentObject: {fileID: 0} 311 | m_PrefabInternal: {fileID: 100100000} 312 | m_GameObject: {fileID: 1572561591838158} 313 | m_Enabled: 1 314 | m_Density: 1 315 | m_Material: {fileID: 0} 316 | m_IsTrigger: 0 317 | m_UsedByEffector: 0 318 | m_UsedByComposite: 0 319 | m_Offset: {x: -0.52, y: -0.78} 320 | m_Size: {x: 0.56, y: 1.27} 321 | m_Direction: 0 322 | --- !u!212 &212073141053345930 323 | SpriteRenderer: 324 | m_ObjectHideFlags: 1 325 | m_PrefabParentObject: {fileID: 0} 326 | m_PrefabInternal: {fileID: 100100000} 327 | m_GameObject: {fileID: 1572561591838158} 328 | m_Enabled: 1 329 | m_CastShadows: 0 330 | m_ReceiveShadows: 0 331 | m_DynamicOccludee: 1 332 | m_MotionVectors: 1 333 | m_LightProbeUsage: 1 334 | m_ReflectionProbeUsage: 1 335 | m_Materials: 336 | - {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0} 337 | m_StaticBatchInfo: 338 | firstSubMesh: 0 339 | subMeshCount: 0 340 | m_StaticBatchRoot: {fileID: 0} 341 | m_ProbeAnchor: {fileID: 0} 342 | m_LightProbeVolumeOverride: {fileID: 0} 343 | m_ScaleInLightmap: 1 344 | m_PreserveUVs: 0 345 | m_IgnoreNormalsForChartDetection: 0 346 | m_ImportantGI: 0 347 | m_StitchLightmapSeams: 0 348 | m_SelectedEditorRenderState: 0 349 | m_MinimumChartSize: 4 350 | m_AutoUVMaxDistance: 0.5 351 | m_AutoUVMaxAngle: 89 352 | m_LightmapParameters: {fileID: 0} 353 | m_SortingLayerID: 0 354 | m_SortingLayer: 0 355 | m_SortingOrder: 0 356 | m_Sprite: {fileID: 21300000, guid: 7dfed40782fc74fb588b40273aba9b80, type: 3} 357 | m_Color: {r: 1, g: 1, b: 1, a: 1} 358 | m_FlipX: 0 359 | m_FlipY: 0 360 | m_DrawMode: 0 361 | m_Size: {x: 4.83, y: 4.84} 362 | m_AdaptiveModeThreshold: 0.5 363 | m_SpriteTileMode: 0 364 | m_WasSpriteAssigned: 1 365 | m_MaskInteraction: 0 366 | -------------------------------------------------------------------------------- /fighter.unity: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!29 &1 4 | OcclusionCullingSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_OcclusionBakeSettings: 8 | smallestOccluder: 5 9 | smallestHole: 0.25 10 | backfaceThreshold: 100 11 | m_SceneGUID: 00000000000000000000000000000000 12 | m_OcclusionCullingData: {fileID: 0} 13 | --- !u!104 &2 14 | RenderSettings: 15 | m_ObjectHideFlags: 0 16 | serializedVersion: 8 17 | m_Fog: 0 18 | m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} 19 | m_FogMode: 3 20 | m_FogDensity: 0.01 21 | m_LinearFogStart: 0 22 | m_LinearFogEnd: 300 23 | m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} 24 | m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} 25 | m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} 26 | m_AmbientIntensity: 1 27 | m_AmbientMode: 3 28 | m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} 29 | m_SkyboxMaterial: {fileID: 0} 30 | m_HaloStrength: 0.5 31 | m_FlareStrength: 1 32 | m_FlareFadeSpeed: 3 33 | m_HaloTexture: {fileID: 0} 34 | m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} 35 | m_DefaultReflectionMode: 0 36 | m_DefaultReflectionResolution: 128 37 | m_ReflectionBounces: 1 38 | m_ReflectionIntensity: 1 39 | m_CustomReflection: {fileID: 0} 40 | m_Sun: {fileID: 0} 41 | m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} 42 | --- !u!157 &3 43 | LightmapSettings: 44 | m_ObjectHideFlags: 0 45 | serializedVersion: 11 46 | m_GIWorkflowMode: 1 47 | m_GISettings: 48 | serializedVersion: 2 49 | m_BounceScale: 1 50 | m_IndirectOutputScale: 1 51 | m_AlbedoBoost: 1 52 | m_TemporalCoherenceThreshold: 1 53 | m_EnvironmentLightingMode: 0 54 | m_EnableBakedLightmaps: 0 55 | m_EnableRealtimeLightmaps: 0 56 | m_LightmapEditorSettings: 57 | serializedVersion: 9 58 | m_Resolution: 2 59 | m_BakeResolution: 40 60 | m_TextureWidth: 1024 61 | m_TextureHeight: 1024 62 | m_AO: 0 63 | m_AOMaxDistance: 1 64 | m_CompAOExponent: 1 65 | m_CompAOExponentDirect: 0 66 | m_Padding: 2 67 | m_LightmapParameters: {fileID: 0} 68 | m_LightmapsBakeMode: 1 69 | m_TextureCompression: 1 70 | m_FinalGather: 0 71 | m_FinalGatherFiltering: 1 72 | m_FinalGatherRayCount: 256 73 | m_ReflectionCompression: 2 74 | m_MixedBakeMode: 2 75 | m_BakeBackend: 0 76 | m_PVRSampling: 1 77 | m_PVRDirectSampleCount: 32 78 | m_PVRSampleCount: 500 79 | m_PVRBounces: 2 80 | m_PVRFilterTypeDirect: 0 81 | m_PVRFilterTypeIndirect: 0 82 | m_PVRFilterTypeAO: 0 83 | m_PVRFilteringMode: 1 84 | m_PVRCulling: 1 85 | m_PVRFilteringGaussRadiusDirect: 1 86 | m_PVRFilteringGaussRadiusIndirect: 5 87 | m_PVRFilteringGaussRadiusAO: 2 88 | m_PVRFilteringAtrousPositionSigmaDirect: 0.5 89 | m_PVRFilteringAtrousPositionSigmaIndirect: 2 90 | m_PVRFilteringAtrousPositionSigmaAO: 1 91 | m_LightingDataAsset: {fileID: 0} 92 | m_UseShadowmask: 1 93 | --- !u!196 &4 94 | NavMeshSettings: 95 | serializedVersion: 2 96 | m_ObjectHideFlags: 0 97 | m_BuildSettings: 98 | serializedVersion: 2 99 | agentTypeID: 0 100 | agentRadius: 0.5 101 | agentHeight: 2 102 | agentSlope: 45 103 | agentClimb: 0.4 104 | ledgeDropHeight: 0 105 | maxJumpAcrossDistance: 0 106 | minRegionArea: 2 107 | manualCellSize: 0 108 | cellSize: 0.16666667 109 | manualTileSize: 0 110 | tileSize: 256 111 | accuratePlacement: 0 112 | debug: 113 | m_Flags: 0 114 | m_NavMeshData: {fileID: 0} 115 | --- !u!1 &1234468788 116 | GameObject: 117 | m_ObjectHideFlags: 0 118 | m_PrefabParentObject: {fileID: 0} 119 | m_PrefabInternal: {fileID: 0} 120 | serializedVersion: 5 121 | m_Component: 122 | - component: {fileID: 1234468789} 123 | m_Layer: 0 124 | m_Name: Bounds 125 | m_TagString: Untagged 126 | m_Icon: {fileID: 0} 127 | m_NavMeshLayer: 0 128 | m_StaticEditorFlags: 0 129 | m_IsActive: 1 130 | --- !u!4 &1234468789 131 | Transform: 132 | m_ObjectHideFlags: 0 133 | m_PrefabParentObject: {fileID: 0} 134 | m_PrefabInternal: {fileID: 0} 135 | m_GameObject: {fileID: 1234468788} 136 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 137 | m_LocalPosition: {x: -3.576929, y: -0.6698367, z: 0.015332876} 138 | m_LocalScale: {x: 1, y: 1, z: 1} 139 | m_Children: 140 | - {fileID: 1385253733} 141 | - {fileID: 1590475646} 142 | - {fileID: 1421958062} 143 | - {fileID: 1738630047} 144 | m_Father: {fileID: 0} 145 | m_RootOrder: 1 146 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 147 | --- !u!1 &1385253730 148 | GameObject: 149 | m_ObjectHideFlags: 0 150 | m_PrefabParentObject: {fileID: 0} 151 | m_PrefabInternal: {fileID: 0} 152 | serializedVersion: 5 153 | m_Component: 154 | - component: {fileID: 1385253733} 155 | - component: {fileID: 1385253732} 156 | - component: {fileID: 1385253731} 157 | m_Layer: 0 158 | m_Name: Bound 159 | m_TagString: Untagged 160 | m_Icon: {fileID: 0} 161 | m_NavMeshLayer: 0 162 | m_StaticEditorFlags: 0 163 | m_IsActive: 1 164 | --- !u!50 &1385253731 165 | Rigidbody2D: 166 | serializedVersion: 4 167 | m_ObjectHideFlags: 0 168 | m_PrefabParentObject: {fileID: 0} 169 | m_PrefabInternal: {fileID: 0} 170 | m_GameObject: {fileID: 1385253730} 171 | m_BodyType: 2 172 | m_Simulated: 1 173 | m_UseFullKinematicContacts: 0 174 | m_UseAutoMass: 0 175 | m_Mass: 1 176 | m_LinearDrag: 0 177 | m_AngularDrag: 0.05 178 | m_GravityScale: 1 179 | m_Material: {fileID: 0} 180 | m_Interpolate: 0 181 | m_SleepingMode: 1 182 | m_CollisionDetection: 0 183 | m_Constraints: 0 184 | --- !u!61 &1385253732 185 | BoxCollider2D: 186 | m_ObjectHideFlags: 0 187 | m_PrefabParentObject: {fileID: 0} 188 | m_PrefabInternal: {fileID: 0} 189 | m_GameObject: {fileID: 1385253730} 190 | m_Enabled: 1 191 | m_Density: 1 192 | m_Material: {fileID: 0} 193 | m_IsTrigger: 0 194 | m_UsedByEffector: 0 195 | m_UsedByComposite: 0 196 | m_Offset: {x: 0, y: 0} 197 | m_SpriteTilingProperty: 198 | border: {x: 0, y: 0, z: 0, w: 0} 199 | pivot: {x: 0, y: 0} 200 | oldSize: {x: 0, y: 0} 201 | newSize: {x: 0, y: 0} 202 | adaptiveTilingThreshold: 0 203 | drawMode: 0 204 | adaptiveTiling: 0 205 | m_AutoTiling: 0 206 | serializedVersion: 2 207 | m_Size: {x: 1, y: 1} 208 | m_EdgeRadius: 0 209 | --- !u!4 &1385253733 210 | Transform: 211 | m_ObjectHideFlags: 0 212 | m_PrefabParentObject: {fileID: 0} 213 | m_PrefabInternal: {fileID: 0} 214 | m_GameObject: {fileID: 1385253730} 215 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 216 | m_LocalPosition: {x: 3.5469291, y: 6.8298364, z: 0.052435413} 217 | m_LocalScale: {x: 20.346346, y: 2.1966374, z: 2.584279} 218 | m_Children: [] 219 | m_Father: {fileID: 1234468789} 220 | m_RootOrder: 0 221 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 222 | --- !u!1 &1421958059 223 | GameObject: 224 | m_ObjectHideFlags: 0 225 | m_PrefabParentObject: {fileID: 0} 226 | m_PrefabInternal: {fileID: 0} 227 | serializedVersion: 5 228 | m_Component: 229 | - component: {fileID: 1421958062} 230 | - component: {fileID: 1421958061} 231 | - component: {fileID: 1421958060} 232 | m_Layer: 0 233 | m_Name: Bound (2) 234 | m_TagString: Untagged 235 | m_Icon: {fileID: 0} 236 | m_NavMeshLayer: 0 237 | m_StaticEditorFlags: 0 238 | m_IsActive: 1 239 | --- !u!50 &1421958060 240 | Rigidbody2D: 241 | serializedVersion: 4 242 | m_ObjectHideFlags: 0 243 | m_PrefabParentObject: {fileID: 0} 244 | m_PrefabInternal: {fileID: 0} 245 | m_GameObject: {fileID: 1421958059} 246 | m_BodyType: 2 247 | m_Simulated: 1 248 | m_UseFullKinematicContacts: 0 249 | m_UseAutoMass: 0 250 | m_Mass: 1 251 | m_LinearDrag: 0 252 | m_AngularDrag: 0.05 253 | m_GravityScale: 1 254 | m_Material: {fileID: 0} 255 | m_Interpolate: 0 256 | m_SleepingMode: 1 257 | m_CollisionDetection: 0 258 | m_Constraints: 0 259 | --- !u!61 &1421958061 260 | BoxCollider2D: 261 | m_ObjectHideFlags: 0 262 | m_PrefabParentObject: {fileID: 0} 263 | m_PrefabInternal: {fileID: 0} 264 | m_GameObject: {fileID: 1421958059} 265 | m_Enabled: 1 266 | m_Density: 1 267 | m_Material: {fileID: 0} 268 | m_IsTrigger: 0 269 | m_UsedByEffector: 0 270 | m_UsedByComposite: 0 271 | m_Offset: {x: 0, y: 0} 272 | m_SpriteTilingProperty: 273 | border: {x: 0, y: 0, z: 0, w: 0} 274 | pivot: {x: 0, y: 0} 275 | oldSize: {x: 0, y: 0} 276 | newSize: {x: 0, y: 0} 277 | adaptiveTilingThreshold: 0 278 | drawMode: 0 279 | adaptiveTiling: 0 280 | m_AutoTiling: 0 281 | serializedVersion: 2 282 | m_Size: {x: 1, y: 1} 283 | m_EdgeRadius: 0 284 | --- !u!4 &1421958062 285 | Transform: 286 | m_ObjectHideFlags: 0 287 | m_PrefabParentObject: {fileID: 0} 288 | m_PrefabInternal: {fileID: 0} 289 | m_GameObject: {fileID: 1421958059} 290 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 291 | m_LocalPosition: {x: 12.37, y: 0.81983674, z: 0.052435413} 292 | m_LocalScale: {x: 2.5432925, y: 13.35694, z: 2.584279} 293 | m_Children: [] 294 | m_Father: {fileID: 1234468789} 295 | m_RootOrder: 2 296 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 297 | --- !u!1 &1590475643 298 | GameObject: 299 | m_ObjectHideFlags: 0 300 | m_PrefabParentObject: {fileID: 0} 301 | m_PrefabInternal: {fileID: 0} 302 | serializedVersion: 5 303 | m_Component: 304 | - component: {fileID: 1590475646} 305 | - component: {fileID: 1590475645} 306 | - component: {fileID: 1590475644} 307 | m_Layer: 0 308 | m_Name: Bound (1) 309 | m_TagString: Untagged 310 | m_Icon: {fileID: 0} 311 | m_NavMeshLayer: 0 312 | m_StaticEditorFlags: 0 313 | m_IsActive: 1 314 | --- !u!50 &1590475644 315 | Rigidbody2D: 316 | serializedVersion: 4 317 | m_ObjectHideFlags: 0 318 | m_PrefabParentObject: {fileID: 0} 319 | m_PrefabInternal: {fileID: 0} 320 | m_GameObject: {fileID: 1590475643} 321 | m_BodyType: 2 322 | m_Simulated: 1 323 | m_UseFullKinematicContacts: 0 324 | m_UseAutoMass: 0 325 | m_Mass: 1 326 | m_LinearDrag: 0 327 | m_AngularDrag: 0.05 328 | m_GravityScale: 1 329 | m_Material: {fileID: 0} 330 | m_Interpolate: 0 331 | m_SleepingMode: 1 332 | m_CollisionDetection: 0 333 | m_Constraints: 0 334 | --- !u!61 &1590475645 335 | BoxCollider2D: 336 | m_ObjectHideFlags: 0 337 | m_PrefabParentObject: {fileID: 0} 338 | m_PrefabInternal: {fileID: 0} 339 | m_GameObject: {fileID: 1590475643} 340 | m_Enabled: 1 341 | m_Density: 1 342 | m_Material: {fileID: 0} 343 | m_IsTrigger: 0 344 | m_UsedByEffector: 0 345 | m_UsedByComposite: 0 346 | m_Offset: {x: 0, y: 0} 347 | m_SpriteTilingProperty: 348 | border: {x: 0, y: 0, z: 0, w: 0} 349 | pivot: {x: 0, y: 0} 350 | oldSize: {x: 0, y: 0} 351 | newSize: {x: 0, y: 0} 352 | adaptiveTilingThreshold: 0 353 | drawMode: 0 354 | adaptiveTiling: 0 355 | m_AutoTiling: 0 356 | serializedVersion: 2 357 | m_Size: {x: 1, y: 1} 358 | m_EdgeRadius: 0 359 | --- !u!4 &1590475646 360 | Transform: 361 | m_ObjectHideFlags: 0 362 | m_PrefabParentObject: {fileID: 0} 363 | m_PrefabInternal: {fileID: 0} 364 | m_GameObject: {fileID: 1590475643} 365 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 366 | m_LocalPosition: {x: 3.5469291, y: -5.5001636, z: 0.052435413} 367 | m_LocalScale: {x: 20.346346, y: 2.1966374, z: 2.584279} 368 | m_Children: [] 369 | m_Father: {fileID: 1234468789} 370 | m_RootOrder: 1 371 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 372 | --- !u!1 &1646335360 373 | GameObject: 374 | m_ObjectHideFlags: 0 375 | m_PrefabParentObject: {fileID: 0} 376 | m_PrefabInternal: {fileID: 0} 377 | serializedVersion: 5 378 | m_Component: 379 | - component: {fileID: 1646335365} 380 | - component: {fileID: 1646335364} 381 | - component: {fileID: 1646335363} 382 | - component: {fileID: 1646335362} 383 | - component: {fileID: 1646335361} 384 | m_Layer: 0 385 | m_Name: Main Camera 386 | m_TagString: MainCamera 387 | m_Icon: {fileID: 0} 388 | m_NavMeshLayer: 0 389 | m_StaticEditorFlags: 0 390 | m_IsActive: 1 391 | --- !u!81 &1646335361 392 | AudioListener: 393 | m_ObjectHideFlags: 0 394 | m_PrefabParentObject: {fileID: 0} 395 | m_PrefabInternal: {fileID: 0} 396 | m_GameObject: {fileID: 1646335360} 397 | m_Enabled: 1 398 | --- !u!124 &1646335362 399 | Behaviour: 400 | m_ObjectHideFlags: 0 401 | m_PrefabParentObject: {fileID: 0} 402 | m_PrefabInternal: {fileID: 0} 403 | m_GameObject: {fileID: 1646335360} 404 | m_Enabled: 1 405 | --- !u!92 &1646335363 406 | Behaviour: 407 | m_ObjectHideFlags: 0 408 | m_PrefabParentObject: {fileID: 0} 409 | m_PrefabInternal: {fileID: 0} 410 | m_GameObject: {fileID: 1646335360} 411 | m_Enabled: 1 412 | --- !u!20 &1646335364 413 | Camera: 414 | m_ObjectHideFlags: 0 415 | m_PrefabParentObject: {fileID: 0} 416 | m_PrefabInternal: {fileID: 0} 417 | m_GameObject: {fileID: 1646335360} 418 | m_Enabled: 1 419 | serializedVersion: 2 420 | m_ClearFlags: 1 421 | m_BackGroundColor: {r: 0.65, g: 0.65, b: 0.65, a: 1} 422 | m_NormalizedViewPortRect: 423 | serializedVersion: 2 424 | x: 0 425 | y: 0 426 | width: 1 427 | height: 1 428 | near clip plane: 0.3 429 | far clip plane: 1000 430 | field of view: 60 431 | orthographic: 1 432 | orthographic size: 5 433 | m_Depth: -1 434 | m_CullingMask: 435 | serializedVersion: 2 436 | m_Bits: 4294967295 437 | m_RenderingPath: -1 438 | m_TargetTexture: {fileID: 0} 439 | m_TargetDisplay: 0 440 | m_TargetEye: 3 441 | m_HDR: 1 442 | m_AllowMSAA: 1 443 | m_ForceIntoRT: 0 444 | m_OcclusionCulling: 1 445 | m_StereoConvergence: 10 446 | m_StereoSeparation: 0.022 447 | --- !u!4 &1646335365 448 | Transform: 449 | m_ObjectHideFlags: 0 450 | m_PrefabParentObject: {fileID: 0} 451 | m_PrefabInternal: {fileID: 0} 452 | m_GameObject: {fileID: 1646335360} 453 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 454 | m_LocalPosition: {x: 0, y: 0, z: -10} 455 | m_LocalScale: {x: 1, y: 1, z: 1} 456 | m_Children: [] 457 | m_Father: {fileID: 0} 458 | m_RootOrder: 0 459 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 460 | --- !u!1 &1738630044 461 | GameObject: 462 | m_ObjectHideFlags: 0 463 | m_PrefabParentObject: {fileID: 0} 464 | m_PrefabInternal: {fileID: 0} 465 | serializedVersion: 5 466 | m_Component: 467 | - component: {fileID: 1738630047} 468 | - component: {fileID: 1738630046} 469 | - component: {fileID: 1738630045} 470 | m_Layer: 0 471 | m_Name: Bound (3) 472 | m_TagString: Untagged 473 | m_Icon: {fileID: 0} 474 | m_NavMeshLayer: 0 475 | m_StaticEditorFlags: 0 476 | m_IsActive: 1 477 | --- !u!50 &1738630045 478 | Rigidbody2D: 479 | serializedVersion: 4 480 | m_ObjectHideFlags: 0 481 | m_PrefabParentObject: {fileID: 0} 482 | m_PrefabInternal: {fileID: 0} 483 | m_GameObject: {fileID: 1738630044} 484 | m_BodyType: 2 485 | m_Simulated: 1 486 | m_UseFullKinematicContacts: 0 487 | m_UseAutoMass: 0 488 | m_Mass: 1 489 | m_LinearDrag: 0 490 | m_AngularDrag: 0.05 491 | m_GravityScale: 1 492 | m_Material: {fileID: 0} 493 | m_Interpolate: 0 494 | m_SleepingMode: 1 495 | m_CollisionDetection: 0 496 | m_Constraints: 0 497 | --- !u!61 &1738630046 498 | BoxCollider2D: 499 | m_ObjectHideFlags: 0 500 | m_PrefabParentObject: {fileID: 0} 501 | m_PrefabInternal: {fileID: 0} 502 | m_GameObject: {fileID: 1738630044} 503 | m_Enabled: 1 504 | m_Density: 1 505 | m_Material: {fileID: 0} 506 | m_IsTrigger: 0 507 | m_UsedByEffector: 0 508 | m_UsedByComposite: 0 509 | m_Offset: {x: 0, y: 0} 510 | m_SpriteTilingProperty: 511 | border: {x: 0, y: 0, z: 0, w: 0} 512 | pivot: {x: 0, y: 0} 513 | oldSize: {x: 0, y: 0} 514 | newSize: {x: 0, y: 0} 515 | adaptiveTilingThreshold: 0 516 | drawMode: 0 517 | adaptiveTiling: 0 518 | m_AutoTiling: 0 519 | serializedVersion: 2 520 | m_Size: {x: 1, y: 1} 521 | m_EdgeRadius: 0 522 | --- !u!4 &1738630047 523 | Transform: 524 | m_ObjectHideFlags: 0 525 | m_PrefabParentObject: {fileID: 0} 526 | m_PrefabInternal: {fileID: 0} 527 | m_GameObject: {fileID: 1738630044} 528 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 529 | m_LocalPosition: {x: -5.16, y: 0.7198367, z: 0.052435413} 530 | m_LocalScale: {x: 2.5432925, y: 13.35694, z: 2.584279} 531 | m_Children: [] 532 | m_Father: {fileID: 1234468789} 533 | m_RootOrder: 3 534 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 535 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | _This tutorial was written for an older version of Arcadia and is not expected to work on the `beta` (of December 2018) release or anything afterwards._ 2 | 3 | # Arcadia for Clojure Programmers 4 | 5 | This tutorial assumes familiarity with Clojure, and a basic understanding of Unity's scene graph and messaging system. 6 | 7 | This is bare-bones, out-of-the-box Arcadia. Many libraries exist that extend its functionality further. 8 | 9 | ## Project Setup 10 | 11 | This tutorial assumes Unity version 2017.2.0f3, but should work in other recent versions. To check your Unity version, go to `Unity > About Unity` in the editor menubar. 12 | 13 | 1. Open Unity and create a new Unity project in 2D mode. The name doesn't matter, but for clarity here we'll refer to it as `fighter-tutorial`. 14 | 2. `cd` into `fighter-tutorial/Assets`. 15 | 3. `git clone https://github.com/arcadia-unity/fighter-tutorial.git .` 16 | 4. `git submodule init` 17 | 5. `git submodule update` 18 | 19 | Tab into Unity (or open it if it was closed). Arcadia will load. 20 | 21 | 1. Once Arcadia has loaded, in the editor menubar select `Arcadia > Build > Internal Namespaces`. This will compile the core Arcadia namespaces for faster startup times. 22 | 2. Open the fighter tutorial scene by going to `File > Open Scene` and selecting `fighter.unity`. 23 | 3. Press the play button at the top of the editor. 24 | 4. Connect to Arcadia using your favorite editor ([instructions here](https://github.com/arcadia-unity/Arcadia/wiki/REPL)). 25 | 26 | If you forgot to create the new project in 2D mode, press the `2D` button in the Scene view. 27 | 28 | ## Overview of the Arcadia Role System 29 | 30 | Arcadia provides an opt-in bridge to the scene graph, representing bundles of state and behavior as persistent maps called 'roles'. The `:state` entry holds data, and the other entries associate Unity [messages](https://docs.unity3d.com/Manual/EventFunctions.html) (also known as "event functions") with `IFn` instances, encoded as `:update`, `:fixed-update`, `:on-collision-enter`, etc (the complete list can be found in the `arcadia.core/hook-types` map). When a message is dispatched to the GameObject, any `IFn`s associated with that message via an attached role will be called. 31 | 32 | Here's an example of a role: 33 | 34 | ```clojure 35 | (def example-role 36 | {:state {:health 10} ; data to attach to the object 37 | :update #'some-update-function ; var, the function value of which will run during the Update message 38 | :on-collision-enter #'some-collision-function} ; var, the function value of which will run during a collision event 39 | ) 40 | ``` 41 | 42 | This role would be attached to a GameObject `obj` like this: 43 | 44 | ```clojure 45 | (role+ obj ::example-role example-role) 46 | ``` 47 | 48 | Here the keys `:update` and `:on-collision-enter` correspond to the [Update](https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnCollisionEnter.html) and [OnCollisionEnter](https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnCollisionEnter.html) Unity messages. Their values are Clojure vars that will be invoked in response to those Unity messages. The value associated with `:update`, in this case the var `#'some-update-function`, will run every frame, because Unity triggers the `Update` message every frame. Similarly, the the value associated with `:on-collision-enter`, here the var `#'some-collision-function`, will run when any GameObject this role is attached to collides with something. The keyword `::example-role` in `role+` is called the _role key_, and is used to look up the `:state` associated with this particular role. 49 | 50 | Anything implementing the `IFn` interface for the correct arity is supported as a value for the message entries; that is, 51 | 52 | ```clojure 53 | {:update (fn [obj k] (UnityEngine.Debug/Log "running update"))} 54 | ``` 55 | 56 | would also work. Clojure vars, which also implement `IFn`, are greatly preferred, however, because they can be dynamically redefined from the REPL, and can [_serialize_](https://docs.unity3d.com/560/Documentation/Manual/script-Serialization.html). 57 | 58 | The parameters expected of the `IFn` associated with a key are determined by the parameters expected of the corresponding Unity event function. A callback should have the same parameters as the corresponding Unity event function, plus an additional first parameter for the GameObject itself, and an additional final parameter for the key. 59 | 60 | For example, the signature of the [OnCollisionEnter](https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnCollisionEnter.html) message is 61 | 62 | ``` 63 | GameObject.OnCollisionEnter(Collision) 64 | ``` 65 | 66 | The signature of a function associated with the OnCollisionEnter message via `:on-collision-enter` is therefore 67 | 68 | ``` 69 | (fn [^GameObject obj, ^UnityEngine.Collision collision, role-key] ...) 70 | ``` 71 | 72 | To take another example, the signature of the [Update](https://docs.unity3d.com/ScriptReference/MonoBehaviour.Update.html) message is 73 | 74 | ``` 75 | GameObject.Update() 76 | ``` 77 | 78 | That is, there are no parameters. The expected signature of a function associated with the Update message via `:update` is therefore 79 | 80 | ``` 81 | (fn [^GameObject obj, role-key] ...) 82 | ``` 83 | 84 | More documentation about the role system can be found [here](https://github.com/arcadia-unity/Arcadia/wiki/Using-Arcadia#hooks-and-state). 85 | 86 | ## Building the player's avatar 87 | 88 | Starter code for the following walkthrough, with the completed file commented out, can be found at `fighter-tutorial/Assets/fighter_tutorial/core.clj`. 89 | 90 | First, we define the `ns` form to set up the namespace. 91 | 92 | ```clojure 93 | (ns fighter-tutorial.core 94 | (:use arcadia.core arcadia.linear) 95 | (:require [arcadia.sugar :as a] ; For augmented destructuring and imperative code 96 | [arcadia.scene :as scn]) ; For keeping track of stuff we put in the scene 97 | (:import [UnityEngine Collider2D Physics ; Heavy interop... 98 | GameObject Input Rigidbody2D 99 | Vector2 Mathf Resources Transform 100 | Collision2D Physics2D] 101 | ArcadiaState)) ; Handles our state 102 | ``` 103 | 104 | Let's start by making an inert GameObject representing the player. We'll do this by [instantiating](https://docs.unity3d.com/Manual/InstantiatingPrefabs.html) the `"fighter"` [prefab](https://docs.unity3d.com/Manual/Prefabs.html). 105 | 106 | ```clojure 107 | (defn setup [] 108 | ;; `retire` any objects registered with the label `::player`, removing them from the scene 109 | (scn/retire ::player) 110 | ;; Load the "fighter" prefab into the scene graph 111 | (let [player (GameObject/Instantiate (Resources/Load "fighter"))] 112 | ;; Register the player GameObject with the label `::player` 113 | (scn/register player ::player) 114 | ;; Set its name 115 | (set! (.name player) "player"))) 116 | ``` 117 | 118 | After evaluating this code, run `(setup)` in the REPL. A new GameObject should appear, looking like this: ![]() 119 | 120 | If we call `(setup)` multiple times at this point, the scene will seem to remain the same, but really we're destroying and recreating the player every time. 121 | 122 | Now let's define some helper functions for input and math. 123 | 124 | ```clojure 125 | (defn bearing-vector [angle] 126 | (let [angle (* Mathf/Deg2Rad angle)] 127 | (v2 (Mathf/Cos angle) (Mathf/Sin angle)))) 128 | 129 | (defn abs-angle [v] 130 | (* Mathf/Rad2Deg 131 | (Mathf/Atan2 (.y v) (.x v)))) 132 | 133 | (defn controller-vector [] 134 | (v2 (Input/GetAxis "Horizontal") 135 | (Input/GetAxis "Vertical"))) 136 | 137 | (defn wasd-key [] 138 | (or (Input/GetKey "w") 139 | (Input/GetKey "a") 140 | (Input/GetKey "s") 141 | (Input/GetKey "d"))) 142 | ``` 143 | 144 | ### Player Movement 145 | 146 | Now we can write the interactive movement logic. 147 | 148 | To review, Arcadia associates state and behavior with GameObjects using maps called _roles_. Roles are attached to GameObjects on a key called the _role key_. 149 | 150 | Roles specify callbacks that run in response to Unity messages, as well as an optional `:state` entry that holds data. 151 | 152 | ```clojure 153 | (defn player-movement-fixed-update [obj k] ; We'll only use the `obj` parameter 154 | (with-cmpt obj [rb Rigidbody2D] ; Gets the Rigidbody2D component 155 | (when (wasd-key) ; Checks for WASD key 156 | (.MoveRotation rb (abs-angle (controller-vector))) ; Rotates towards key 157 | (set! (.angularVelocity rb) 0) 158 | (.AddForce rb ; Moves forwards 159 | (v2* (bearing-vector (.rotation rb)) 160 | 3))))) 161 | 162 | ;; Associates the FixedUpdate Unity message with a var in a role map 163 | (def player-movement-role 164 | {:fixed-update #'player-movement-fixed-update}) 165 | ``` 166 | 167 | Roles, in turn, can be gathered together into maps, and the roles in these maps attached to GameObjects with `roles+`. 168 | 169 | ```clojure 170 | ;; Packages the role up in a map with a descriptive key 171 | (def player-roles 172 | {::movement player-movement-role}) 173 | ``` 174 | 175 | Note that there is no deep need to have a separate `player-movement-role` var, for our purposes here the following would work just as well: 176 | 177 | ```clojure 178 | (def player-roles 179 | {::movement {:fixed-update #'player-movement-fixed-update}}) 180 | ``` 181 | 182 | Finally, we modify the `setup` function to attach the state and behavior specified in `player-roles`. We'll continue to modify this function as we add features to the game. 183 | 184 | ```clojure 185 | (defn setup [] 186 | (scn/retire ::player) 187 | (let [player (GameObject/Instantiate (Resources/Load "fighter"))] 188 | (scn/register player ::player) 189 | (set! (.name player) "player") 190 | (roles+ player player-roles))) ; NEW 191 | ``` 192 | 193 | From the REPL, call `(setup)` again. Back in the Unity Game view, the player should now be controllable using the `w` `a` `s` `d` keys. 194 | 195 | ## Bullets 196 | 197 | Now let's shoot some bullets. We want the fighter to launch a bullet every time the player hits the space key. We also want to clean up bullets after a certain amount of time. 198 | 199 | We'll need: 200 | 201 | - A function that "shoots" a bullet, placing it in the scene graph 202 | - A role that moves the bullet forward every physics frame 203 | - A role that calls the shooting function when the player hits space 204 | - A role responsible for removing bullets after a certain period of time. 205 | 206 | Let's start with the time restriction, so bullets don't pile up. 207 | 208 | We could define a role for this the way we've been doing, like so: 209 | 210 | ```clojure 211 | (defn lifespan-update [obj k] 212 | (let [{:keys [start lifespan]} (state obj k)] 213 | (when (< lifespan (.TotalMilliseconds (.Subtract System.DateTime/Now start))) 214 | (retire obj)))) 215 | 216 | (def lifespan-role 217 | {:state {:start System.DateTime/Now 218 | :lifespan 0} 219 | :update #'lifespan-update}) 220 | ``` 221 | 222 | This can get a little tedious, however. Arcadia provides a `defrole` macro to speed the process of defining roles. 223 | 224 | ```clojure 225 | (defrole lifespan-role 226 | :state {:start System.DateTime/Now 227 | :lifespan 0} 228 | (update [obj k] 229 | (let [{:keys [start lifespan]} (state obj k)] 230 | (when (< lifespan (.TotalMilliseconds (.Subtract System.DateTime/Now start))) 231 | (retire obj))))) 232 | ``` 233 | 234 | We'll use `defrole` from now on. 235 | 236 | Bullet movement is just: 237 | 238 | ```clojure 239 | (defrole bullet-movement-role 240 | (fixed-update [bullet k] 241 | (with-cmpt bullet [rb Rigidbody2D] 242 | (move-forward rb 0.2)))) 243 | ``` 244 | 245 | Finally, the roles map for bullets: 246 | 247 | ```clojure 248 | (def bullet-roles 249 | {::movement bullet-movement-role 250 | ::lifespan lifespan-role}) 251 | ``` 252 | 253 | We would like to share the shooting logic with both the player and non-player entities. We'll use two functions, `shoot-bullet` and `shoot`. `shoot-bullet` takes a `UnityEngine.Vector2` starting position `start` and an angle `bearing`, and creates a new bullet at that position and angle, returning the bullet. 254 | 255 | `shoot` takes a GameObject and shoots a bullet forward from it, set to ignore collisions with the GameObject itself. 256 | 257 | ```clojure 258 | (defn shoot-bullet [start bearing] 259 | (let [bullet (GameObject/Instantiate 260 | (Resources/Load "missile" GameObject))] 261 | (with-cmpt bullet [rb Rigidbody2D, 262 | tr Transform] 263 | (scn/register bullet ::bullet) 264 | (set! (.position tr) (v3 (.x start) (.y start) 1)) 265 | (.MoveRotation rb bearing) 266 | (roles+ bullet 267 | (-> bullet-roles 268 | (assoc-in [::lifespan :state :start] System.DateTime/Now) 269 | (assoc-in [::lifespan :state :lifespan] 2000))) 270 | bullet))) 271 | 272 | (defn shoot [obj layer] 273 | (with-cmpt obj [rb Rigidbody2D] 274 | (let [bullet (shoot-bullet (.position rb) (.rotation rb))] 275 | (set! (.layer bullet) layer) 276 | bullet))) 277 | ``` 278 | 279 | Now we give the player the ability to shoot bullets by hitting space: 280 | 281 | ```clojure 282 | (defrole player-shooting-role 283 | (update [obj k] 284 | (with-cmpt obj [rb Rigidbody2D] 285 | (when (Input/GetKeyDown "space") 286 | (shoot obj player-bullets-layer))))) 287 | ``` 288 | 289 | We add this functionality to the player by going back and editing `player-roles`: 290 | 291 | ```clojure 292 | (def player-roles 293 | {::movement player-movement-role 294 | ::shooting player-shooting-role}) ; NEW 295 | ``` 296 | 297 | ## The enemy 298 | 299 | We can create the enemy using the same technique: define roles, attach them to a GameObject. Note the reuse of `shoot`. 300 | 301 | ```clojure 302 | ;; enemy shooting 303 | (defrole enemy-shooting-role 304 | :state {:last-shot System.DateTime/Now} 305 | (update [obj k] 306 | (let [{:keys [target last-shot]} (state obj k) 307 | now System.DateTime/Now] 308 | (when (and (obj-nil target) ; check that the target is neither nil nor a null object 309 | (< 1000 (.TotalMilliseconds (.Subtract now last-shot)))) 310 | (update-state obj k assoc :last-shot now) 311 | (shoot obj enemy-bullets-layer))))) 312 | 313 | ;; enemy movement 314 | (defrole enemy-movement-role 315 | :state {:target nil} 316 | (fixed-update [obj k] 317 | ;; Make sure the target is neither nil nor the null object using 318 | ;; `obj-nil` 319 | (when-let [target (obj-nil (:target (state obj k)))] 320 | ;; We're going to use augmented destructuring to access 321 | ;; components of GameObjects using `arcadia.sugar/with-cmpt`, 322 | ;; and access object properties and fields using 323 | ;; `arcadia.sugar/o`. See the docs for `arcadia.sugar/let` for 324 | ;; more details. 325 | (a/let [(a/with-cmpt rb1 Rigidbody2D) obj ; Get the Rigidbody2D component 326 | (a/o pos1 position, rot1 rotation) rb1 ; Get the position and rotation from it 327 | (a/with-cmpt (a/o pos2 position) Rigidbody2D) target ; Get the position of the target's Rigidbody2D 328 | pos-diff (v2- pos2 pos1) ; Get the difference vector from the object to the target 329 | rot-diff (Vector2/SignedAngle ; Get the rotation needed to face the target 330 | (bearing-vector rot1) 331 | pos-diff)] 332 | (.MoveRotation rb1 ; rotate the Rigidbody2D towards the target, clamped to one degree per frame 333 | (+ rot1 (Mathf/Clamp -1 rot-diff 1))))))) 334 | 335 | (def enemy-roles 336 | {::shooting enemy-shooting-role 337 | ::movement enemy-movement-role}) 338 | 339 | ;; function to construct the enemy 340 | (defn make-enemy [protagonist] 341 | (let [enemy (GameObject/Instantiate (Resources/Load "villain" GameObject))] 342 | (scn/register enemy ::enemy) 343 | (roles+ enemy 344 | (-> enemy-roles 345 | (assoc-in [::movement :state :target] protagonist) 346 | (assoc-in [::shooting :state :target] protagonist))))) 347 | ``` 348 | 349 | Now we can add the enemy to the `setup` function: 350 | 351 | ```clojure 352 | (defn setup [] 353 | (scn/retire ::enemy ::bullet ::player) 354 | (let [player (GameObject/Instantiate (Resources/Load "fighter"))] 355 | (scn/register player ::player) 356 | (set! (.name player) "player") 357 | (roles+ player player-roles) 358 | (make-enemy player))) ; NEW 359 | ``` 360 | 361 | ### Damage 362 | 363 | To represent characters capable of taking damage, we can add a `:health` key to state and some logic to remove a player when the health reaches zero. We also define a `damage` function that removes health from an entity. 364 | 365 | ```clojure 366 | ;; health, remove from scene when zero 367 | ;; expects to be keyed at ::health 368 | (defrole health-role 369 | :state {:health 1} 370 | (update [obj k] 371 | (let [{:keys [health]} (state obj k)] 372 | (when (<= health 0) 373 | (retire obj))))) 374 | 375 | (defn damage [obj amt] 376 | (update-state obj ::health update :health - amt)) 377 | ``` 378 | 379 | We can then set a role for bullets by which they remove health from entities they collide with. 380 | 381 | ```clojure 382 | (defrole bullet-collision 383 | (on-trigger-enter2d [bullet, ^Collider2D collider, k] 384 | (let [obj2 (.gameObject collider)] 385 | (when (state obj2 ::health) 386 | (damage obj2 1) 387 | (retire bullet))))) 388 | ``` 389 | 390 | Now we add this role to `bullet-roles`: 391 | 392 | ```clojure 393 | (def bullet-roles 394 | {::movement bullet-movement-role 395 | ::lifespan lifespan-role 396 | ::collision bullet-collision}) ; NEW 397 | ``` 398 | 399 | To make the player susceptible to the enemy's bullets, we need only give it the `health-role`: 400 | 401 | ```clojure 402 | (def player-roles 403 | {::movement player-movement-role 404 | ::shooting player-shooting-role 405 | ::health (update health-role :state assoc :health 10)}) ; NEW 406 | ``` 407 | 408 | To make the enemy susceptible to the player's bullets, we do the same: 409 | 410 | ```clojure 411 | (def enemy-roles 412 | {::shooting enemy-shooting-role 413 | ::movement enemy-movement-role 414 | ::health (update health-role :state assoc :health 10)}) ; NEW 415 | ``` 416 | --------------------------------------------------------------------------------