├── .gitignore ├── Part1 ├── Part1.arproj ├── patches │ └── Position Corner Mouth.arp └── scripts │ ├── functions.js │ └── script.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /Part1/Part1.arproj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulienHe/SparkGameTutorial/6378dab4ce8bc0d6dd6aea630d7c18751bf1afd5/Part1/Part1.arproj -------------------------------------------------------------------------------- /Part1/patches/Position Corner Mouth.arp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulienHe/SparkGameTutorial/6378dab4ce8bc0d6dd6aea630d7c18751bf1afd5/Part1/patches/Position Corner Mouth.arp -------------------------------------------------------------------------------- /Part1/scripts/functions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if at least one mouth point inside a Canvas / Rectangle. 3 | * This will be used to determine if the user is gaining point or not 4 | * @param {Number} mouthX Position on X axis of the mouth point 5 | * @param {Number} mouthY Position on Y axis of the mouth point 6 | * @param {Number} leftPoint Left edge of our Canvas / Rectangle 7 | * @param {Number} rightPoint Right edge of our Canvas / Rectangle 8 | * @param {Number} topPoint Top edge of our Canvas / Rectangle 9 | * @param {Number} bottomPoint Bottom edge of our Canvas / Rectangle 10 | * @return {Boolean} True / False 11 | */ 12 | export function isInMouth(mouthX, mouthY, leftPoint, rightPoint, topPoint, bottomPoint) { 13 | if (mouthX > leftPoint && mouthX < rightPoint) { 14 | if (mouthY > bottomPoint && mouthY < topPoint) { 15 | return true; 16 | } else { 17 | return false; 18 | } 19 | } else { 20 | return false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Part1/scripts/script.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Julien Henrotte @julien_he 3 | * Company: UltraSuperNew http://ultrasupernew.com/ 4 | */ 5 | 6 | /** 7 | * Modules 8 | */ 9 | const Scene = require('Scene'); 10 | const FaceTracking = require('FaceTracking'); 11 | const Patches = require('Patches'); 12 | const Diagnostics = require('Diagnostics'); 13 | const Time = require('Time'); 14 | 15 | import { isInMouth } from './functions.js'; 16 | 17 | const root = Scene.root; // Just a shorthand 18 | const face = FaceTracking.face(0); // Track the first face that appear on the screen 19 | const cube = root.find('cube'); 20 | 21 | /** 22 | * Points position of the mouth. 23 | * Init now to use it later on 24 | */ 25 | const mouthPoint = { 26 | leftX: face.mouth.leftCorner.x, 27 | leftY: face.mouth.leftCorner.y, 28 | righttX: face.mouth.rightCorner.x, 29 | rightY: face.mouth.rightCorner.y, 30 | topX: face.mouth.upperLipCenter.x, 31 | topY: face.mouth.upperLipCenter.y, 32 | bottomX: face.mouth.lowerLipCenter.x, 33 | bottomY: face.mouth.lowerLipCenter.y 34 | }; 35 | 36 | /** 37 | * HArdcoded value of the Device Height 38 | */ 39 | const heightDeviceSpatial = 0.5; 40 | 41 | /** 42 | * Editable 43 | */ 44 | let isMouthOpen = false; 45 | let screenSizeHeight = 0, 46 | screenScaleValue = 0; // Device sizes 47 | 48 | /** 49 | * Get the size / scale of the screen 50 | */ 51 | const screenSizeHeightPX = Patches.getScalarValue('screenSizeHeightPX'); 52 | const screenScale = Patches.getScalarValue('screenScale'); 53 | 54 | /** 55 | * Current Mouth point location 56 | */ 57 | const leftMouthPoint = Patches.getVectorValue('leftMouthPointScript'); 58 | const rightMouthPoint = Patches.getVectorValue('rightMouthPointScript'); 59 | const topMouthPoint = Patches.getVectorValue('topMouthPointScript'); 60 | const bottomMouthPoint = Patches.getVectorValue('bottomMouthPointScript'); 61 | 62 | /** 63 | * Subscribe to the mouth open event 64 | */ 65 | FaceTracking.face(0) 66 | .mouth.openness.monitor() 67 | .subscribe(function(event) { 68 | if (event.newValue > 0.4) { 69 | // When the mouth is open, we update the position of each corner and send it back to Spark AR. 70 | Patches.setScalarValue('mouthleftXCorner', mouthPoint.leftX); 71 | Patches.setScalarValue('mouthleftYCorner', mouthPoint.leftY); 72 | 73 | Patches.setScalarValue('mouthRightXCorner', mouthPoint.righttX); 74 | Patches.setScalarValue('mouthRightYCorner', mouthPoint.rightY); 75 | 76 | Patches.setScalarValue('topLipsCenterX', mouthPoint.topX); 77 | Patches.setScalarValue('topLipsCenterY', mouthPoint.topY); 78 | 79 | Patches.setScalarValue('bottomLipsCenterX', mouthPoint.bottomX); 80 | Patches.setScalarValue('bottomLipsCenterY', mouthPoint.bottomY); 81 | if (isMouthOpen === false) { 82 | isMouthOpen = true; 83 | } 84 | } else { 85 | if (isMouthOpen === true) { 86 | const isElementEaten = isAte(cube, 100); 87 | if (isElementEaten === true ) { 88 | Diagnostics.log('You are eating it'); 89 | } else { 90 | Diagnostics.log('You missed it'); 91 | } 92 | isMouthOpen = false; 93 | } 94 | } 95 | }); 96 | 97 | 98 | /** 99 | * Init the Size and Scale Variable 100 | */ 101 | function setDeviceSize() { 102 | screenSizeHeight = screenSizeHeightPX.pinLastValue(); 103 | screenScaleValue = screenScale.pinLastValue(); 104 | } 105 | const timeoutTimer = Time.setTimeout(setDeviceSize, 100); 106 | 107 | 108 | /** 109 | * Get Corner and compare to see if mouth is inside a box 110 | * @param {Object} item // The scoop we want 111 | * @param {Number} size // Size of the element 112 | * @returns {Boolean} // True of false 113 | */ 114 | function isAte(item, size) { 115 | const x = item.transform.position.x.pinLastValue(); 116 | const y = item.transform.position.y.pinLastValue(); 117 | const pourcent = size / 2 / (screenSizeHeight / screenScaleValue); 118 | const easyMath = heightDeviceSpatial * 100; 119 | const ratioToAddSub = (easyMath * pourcent) / 100; 120 | 121 | const leftPos = x - ratioToAddSub; 122 | const rightPos = x + ratioToAddSub; 123 | const topPos = y + ratioToAddSub; 124 | const bottomPos = y - ratioToAddSub; 125 | 126 | // Get each X / Y point of the mouth 127 | const mouthLeftXPos = leftMouthPoint.x.pinLastValue(); 128 | const mouthLeftYPos = leftMouthPoint.y.pinLastValue(); 129 | 130 | const mouthRightXPos = rightMouthPoint.x.pinLastValue(); 131 | const mouthRightYPos = rightMouthPoint.y.pinLastValue(); 132 | 133 | const mouthTopXPos = topMouthPoint.x.pinLastValue(); 134 | const mouthTopYPos = topMouthPoint.y.pinLastValue(); 135 | 136 | const mouthBottomXPos = bottomMouthPoint.x.pinLastValue(); 137 | const mouthBottomYPos = bottomMouthPoint.y.pinLastValue(); 138 | 139 | // Check if we eat the element 140 | const checkLeftBox = isInMouth(mouthLeftXPos, mouthLeftYPos, leftPos, rightPos, topPos, bottomPos); 141 | const checkRigttBox = isInMouth(mouthRightXPos, mouthRightYPos, leftPos, rightPos, topPos, bottomPos); 142 | const checkTopBox = isInMouth(mouthTopXPos, mouthTopYPos, leftPos, rightPos, topPos, bottomPos); 143 | const checkBottomBox = isInMouth(mouthBottomXPos, mouthBottomYPos, leftPos, rightPos, topPos, bottomPos); 144 | 145 | if (checkLeftBox || checkRigttBox || checkTopBox || checkBottomBox) { 146 | return true; 147 | } else { 148 | return false; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spark AR Game tutorial 2 | 3 | This repository is use with a serie of articles designed to explain how to make a game engine for Spark AR. 4 | --------------------------------------------------------------------------------