From 599c06c5c9124814e760d0fa0e90143278f612cb Mon Sep 17 00:00:00 2001 From: jakory Date: Tue, 9 Aug 2016 10:30:36 -0400 Subject: [PATCH 1/2] Make display resolution dialog hidden by default This is in the project settings for PC, Mac, and Linux builds. --- .../Assets/scenes/social-stories.unity | 161 +----------------- .../ProjectSettings/ProjectSettings.asset | 4 +- 2 files changed, 9 insertions(+), 156 deletions(-) diff --git a/sar-opal-base/Assets/scenes/social-stories.unity b/sar-opal-base/Assets/scenes/social-stories.unity index cc8a686..ad34d3b 100644 --- a/sar-opal-base/Assets/scenes/social-stories.unity +++ b/sar-opal-base/Assets/scenes/social-stories.unity @@ -304,12 +304,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 302e34e95607f46b4aee7f72076822ae, type: 3} m_Name: m_EditorClassIdentifier: - audioSource: {fileID: 0} - animator: {fileID: 0} - checkAudio: 0 - checkAnim: 0 - currAnim: 44656661756c74 - playingAnim: 0 --- !u!58 &450361074 CircleCollider2D: m_ObjectHideFlags: 0 @@ -416,10 +410,10 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: Name: 4d6f766561626c657343616d657261 - layerMask: {} - _camera: {fileID: 734616466} + layerMask: + serializedVersion: 2 + m_Bits: 4294967295 layerIds: 00000000 - tmpHitTestList: [] --- !u!81 &734616463 AudioListener: m_ObjectHideFlags: 0 @@ -529,16 +523,9 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: a5315b0af36ac499b92d399a46b73901, type: 3} m_Name: m_EditorClassIdentifier: - gestureManager: {fileID: 0} - sidekickScript: {fileID: 0} - fader: {fileID: 0} - demo: 1 - story: 0 pagesInStory: 0 - socialStories: 1 - incorrectFeedback: [] - correctFeedback: {fileID: 0} slotWidth: 0 + answerSlotWidth: 1 scenesInOrder: 1 --- !u!114 &950648935 MonoBehaviour: @@ -551,7 +538,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 02c261b21f1b948948841603b247267d, type: 3} m_Name: m_EditorClassIdentifier: - mgc: {fileID: 0} --- !u!1 &1288011094 GameObject: m_ObjectHideFlags: 0 @@ -580,10 +566,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: allowTouch: 1 - highlight: {fileID: 0} - mainCam: {fileID: 0} demo: 0 - demospeech: 0 story: 0 pagesInStory: 0 socialStories: 1 @@ -740,10 +723,10 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: Name: 4d61696e2043616d657261 - layerMask: {} - _camera: {fileID: 1648052064} + layerMask: + serializedVersion: 2 + m_Bits: 4294967295 layerIds: 00000000 - tmpHitTestList: [] --- !u!1001 &1802569877 Prefab: m_ObjectHideFlags: 0 @@ -855,7 +838,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 11ea9930ecb674732bee27116520fad8, type: 3} m_Name: m_EditorClassIdentifier: - cachedTransform: {fileID: 0} advancedProps: 0 minTouches: 0 maxTouches: 0 @@ -866,35 +848,11 @@ MonoBehaviour: sendMessageTarget: {fileID: 0} requireGestureToFail: {fileID: 0} friendlyGestures: [] - numTouches: 0 - layer: {fileID: 0} - gestureManagerInstance: {fileID: 0} - delayedStateChange: 0 - requiredGestureFailed: 0 - state: 0 - cachedScreenPosition: {x: 0, y: 0} - cachedPreviousScreenPosition: {x: 0, y: 0} - k__BackingField: 0 - k__BackingField: 0 - minScreenPointsPixelDistance: 0 - minScreenPointsPixelDistanceSquared: 0 - screenTransformPixelThreshold: 0 - screenTransformPixelThresholdSquared: 0 - deltaPosition: {x: 0, y: 0, z: 0} - deltaRotation: 0 - deltaScale: 0 - screenPixelTranslationBuffer: {x: 0, y: 0} - screenPixelRotationBuffer: 0 - angleBuffer: 0 - screenPixelScalingBuffer: 0 - scaleBuffer: 0 - isTransforming: 0 type: 1 minScreenPointsDistance: 0.5 screenTransformThreshold: 0.1 projection: 0 projectionPlaneNormal: {x: 0, y: 0, z: 1} - projectionLayer: {fileID: 0} --- !u!212 &1814297113 SpriteRenderer: m_ObjectHideFlags: 0 @@ -949,7 +907,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 7a878a6ff128243dfb1d89ca0273f059, type: 3} m_Name: m_EditorClassIdentifier: - cachedTransform: {fileID: 0} advancedProps: 0 minTouches: 0 maxTouches: 0 @@ -960,24 +917,9 @@ MonoBehaviour: sendMessageTarget: {fileID: 0} requireGestureToFail: {fileID: 0} friendlyGestures: [] - numTouches: 0 - layer: {fileID: 0} - gestureManagerInstance: {fileID: 0} - delayedStateChange: 0 - requiredGestureFailed: 0 - state: 0 - cachedScreenPosition: {x: 0, y: 0} - cachedPreviousScreenPosition: {x: 0, y: 0} - k__BackingField: 0 - k__BackingField: 0 numberOfTapsRequired: 1 timeLimit: Infinity distanceLimit: Infinity - distanceLimitInPixelsSquared: 0 - isActive: 0 - tapsDone: 0 - startPosition: {x: 0, y: 0} - totalMovement: {x: 0, y: 0} --- !u!114 &1814297116 MonoBehaviour: m_ObjectHideFlags: 0 @@ -989,7 +931,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: c6be551879cd14d739b0188844ef2c60, type: 3} m_Name: m_EditorClassIdentifier: - cachedTransform: {fileID: 0} advancedProps: 0 minTouches: 0 maxTouches: 0 @@ -1000,16 +941,6 @@ MonoBehaviour: sendMessageTarget: {fileID: 0} requireGestureToFail: {fileID: 0} friendlyGestures: [] - numTouches: 0 - layer: {fileID: 0} - gestureManagerInstance: {fileID: 0} - delayedStateChange: 0 - requiredGestureFailed: 0 - state: 0 - cachedScreenPosition: {x: 0, y: 0} - cachedPreviousScreenPosition: {x: 0, y: 0} - k__BackingField: 0 - k__BackingField: 0 ignoreChildren: 0 --- !u!114 &1814297118 MonoBehaviour: @@ -1100,7 +1031,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 978a486d8ecf8437cbb87e8534908895, type: 3} m_Name: m_EditorClassIdentifier: - cachedTransform: {fileID: 0} --- !u!1001 &1899990904 Prefab: m_ObjectHideFlags: 0 @@ -1189,7 +1119,6 @@ GameObject: - 114: {fileID: 2051130764} - 60: {fileID: 2051130765} - 114: {fileID: 2051130766} - - 114: {fileID: 2051130756} m_Layer: 10 m_Name: Dragon m_TagString: PlayObject @@ -1251,7 +1180,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 7a878a6ff128243dfb1d89ca0273f059, type: 3} m_Name: m_EditorClassIdentifier: - cachedTransform: {fileID: 0} advancedProps: 0 minTouches: 0 maxTouches: 0 @@ -1262,72 +1190,9 @@ MonoBehaviour: sendMessageTarget: {fileID: 0} requireGestureToFail: {fileID: 0} friendlyGestures: [] - numTouches: 0 - layer: {fileID: 0} - gestureManagerInstance: {fileID: 0} - delayedStateChange: 0 - requiredGestureFailed: 0 - state: 0 - cachedScreenPosition: {x: 0, y: 0} - cachedPreviousScreenPosition: {x: 0, y: 0} - k__BackingField: 0 - k__BackingField: 0 numberOfTapsRequired: 1 timeLimit: Infinity distanceLimit: Infinity - distanceLimitInPixelsSquared: 0 - isActive: 0 - tapsDone: 0 - startPosition: {x: 0, y: 0} - totalMovement: {x: 0, y: 0} ---- !u!114 &2051130756 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 2051130752} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 74ae431eff8434b0897d3f7f1cff4311, type: 3} - m_Name: - m_EditorClassIdentifier: - cachedTransform: {fileID: 0} - advancedProps: 0 - minTouches: 0 - maxTouches: 0 - combineTouches: 0 - combineTouchesInterval: 0.3 - useSendMessage: 0 - sendStateChangeMessages: 0 - sendMessageTarget: {fileID: 0} - requireGestureToFail: {fileID: 0} - friendlyGestures: [] - numTouches: 0 - layer: {fileID: 0} - gestureManagerInstance: {fileID: 0} - delayedStateChange: 0 - requiredGestureFailed: 0 - state: 0 - cachedScreenPosition: {x: 0, y: 0} - cachedPreviousScreenPosition: {x: 0, y: 0} - k__BackingField: 0 - k__BackingField: 0 - minScreenPointsPixelDistance: 0 - minScreenPointsPixelDistanceSquared: 0 - screenTransformPixelThreshold: 0 - screenTransformPixelThresholdSquared: 0 - deltaPosition: {x: 0, y: 0, z: 0} - deltaRotation: 0 - deltaScale: 0 - screenPixelTranslationBuffer: {x: 0, y: 0} - screenPixelRotationBuffer: 0 - angleBuffer: 0 - screenPixelScalingBuffer: 0 - scaleBuffer: 0 - isTransforming: 0 - type: 1 - minScreenPointsDistance: 0.5 - screenTransformThreshold: 0.1 --- !u!50 &2051130757 Rigidbody2D: serializedVersion: 2 @@ -1430,7 +1295,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 978a486d8ecf8437cbb87e8534908895, type: 3} m_Name: m_EditorClassIdentifier: - cachedTransform: {fileID: 0} --- !u!114 &2051130762 MonoBehaviour: m_ObjectHideFlags: 0 @@ -1550,7 +1414,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: c6be551879cd14d739b0188844ef2c60, type: 3} m_Name: m_EditorClassIdentifier: - cachedTransform: {fileID: 0} advancedProps: 0 minTouches: 0 maxTouches: 0 @@ -1561,14 +1424,4 @@ MonoBehaviour: sendMessageTarget: {fileID: 0} requireGestureToFail: {fileID: 0} friendlyGestures: [] - numTouches: 0 - layer: {fileID: 0} - gestureManagerInstance: {fileID: 0} - delayedStateChange: 0 - requiredGestureFailed: 0 - state: 0 - cachedScreenPosition: {x: 0, y: 0} - cachedPreviousScreenPosition: {x: 0, y: 0} - k__BackingField: 0 - k__BackingField: 0 ignoreChildren: 0 diff --git a/sar-opal-base/ProjectSettings/ProjectSettings.asset b/sar-opal-base/ProjectSettings/ProjectSettings.asset index 0d2d7fe..a09dc55 100644 --- a/sar-opal-base/ProjectSettings/ProjectSettings.asset +++ b/sar-opal-base/ProjectSettings/ProjectSettings.asset @@ -28,7 +28,7 @@ PlayerSettings: iosShowActivityIndicatorOnLoading: -1 androidShowActivityIndicatorOnLoading: -1 iosAppInBackgroundBehavior: 0 - displayResolutionDialog: 1 + displayResolutionDialog: 2 iosAllowHTTPDownload: 1 allowedAutorotateToPortrait: 1 allowedAutorotateToPortraitUpsideDown: 1 @@ -46,7 +46,7 @@ PlayerSettings: submitAnalytics: 1 usePlayerLog: 1 bakeCollisionMeshes: 0 - forceSingleInstance: 0 + forceSingleInstance: 1 resizableWindow: 0 useMacAppStoreValidation: 0 gpuSkinning: 0 From a134860bff51e6ac785aa81f511b419a09c7cc9e Mon Sep 17 00:00:00 2001 From: jakory Date: Tue, 9 Aug 2016 16:41:03 -0400 Subject: [PATCH 2/2] Fix graphics scaling to use camera view - Fix how the game window and graphics are scaled by changing the size of the camera viewport instead of scaling each graphic individually. Now scaling should work for all games (not just social stories games), though if the game is told to load a graphic at a position outside of the viewable area, it will still not be viewable. - Update loading a social stories game to use the new scaling. - While there, fix style of comments to wrap at 80 chars, start with a capitalized word, and end in a period. - Update TODO list in README. --- README.md | 28 +- .../Assets/scripts/MainGameController.cs | 262 +++++++++++------- .../Assets/scripts/PlayObjectProperties.cs | 23 ++ .../Assets/scripts/SceneObjectProperties.cs | 11 +- sar-opal-base/Assets/scripts/Utilities.cs | 91 +++++- 5 files changed, 301 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index f1b89a7..275feaa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# SAR-opal-base +# SAR-opal-base Opal is a generalized Unity game builder designed for use in child-robot interactions. Easily load different graphics for games requiring similar @@ -54,7 +54,7 @@ handles 3D acceleration. One possible solution is to run the executable from the command line with the flag `-force-opengl`, i.e., `./executable-name -force-opengl`. -### Configuration options +### Configuration options - server: [string] the IP address or hostname of the ROS server - port: [string] port number to use @@ -63,7 +63,7 @@ the command line with the flag `-force-opengl`, i.e., `./executable-name calls to the ROS topic "/opal\_tablet". - opal\_action\_topic: [string] the ROS topic to publish OpalAction messages to - opal\_audio\_topic: [string] the ROS topic to publish Bool messages to - indicating whether the sidekick character is done playing audio or not + indicating whether the sidekick character is done playing audio or not - opal\_command\_topic: [string] the ROS topic to subscribe to to receive OpalCommand messages - opal\_log\_topic: [string] the ROS topic to publish String messages to with @@ -119,7 +119,7 @@ The game publishes this message will only be published after receiving a "request keyframe" command - see [/sar\_opal\_msgs](https://github.com/personal-robots/sar_opal_msgs -"/sar\_opal\_msgs") for more info. +"/sar\_opal\_msgs") for more info. The game publishes "/std\_msgs/Bool" to the ROS topic `opal\_tablet\_audio`, to indicate whether the sidekick character is done playing back an audio file. @@ -171,7 +171,7 @@ if you want their source, extra examples, prefabs, etc, then you can. [TouchScript] (https://github.com/TouchScript/TouchScript "TouchScript") makes it easy to detect and respond to touch events, such as taps and drags. See the wiki [here] (https://github.com/TouchScript/TouchScript/wiki "TouchScript -wiki") for more information. +wiki") for more information. We built a unitypackage from the TouchScript 8.1 source, which has been imported into the game in the Assets folder. @@ -184,7 +184,7 @@ to Contribute"). For 8.1, the steps are: - init and update any submodules of those submodules - run `Build/build_external.sh` - run `Build/package.sh` -- import the generated TouchScript.unitypackage file in the Unity editor +- import the generated TouchScript.unitypackage file in the Unity editor Note that the MainCamera and the Moveables Camera in the Unity scene each need a CameraLayer2D component attached. The camera layer is used to "see" which @@ -245,12 +245,12 @@ comments on the github gist page added in. by scripts by default, the polygon collider needing to read the texture to figure out the outline to make the collider the right shape, but not being able to, and thus the collider ending up the wrong shape and making - collisions happen weird... + collisions happen weird... - Only a small set of "demo" graphics are included in this repository. The full set of graphics for different games is available from the Personal Robots Group - email students in the group to inquire. Add the full set to the - `Resources/base_images` folder. + `Resources/base_images` folder. ## Version Notes @@ -284,10 +284,10 @@ To build and deploy the demo version, do the following: settings so you can have both side by side. 3. In the MainGameController, set the flag `demo` to true, `story` to false, and `socialStories` to false. -4. Build and deploy. +4. Build and deploy. The demo version of the game requires some graphics that are not included in -the demo. +the demo. ### Storybook Version @@ -323,7 +323,7 @@ To build and deploy the Social Stories version, do the following: version of Opal deployed on your device, you'll want to change these settings so you can have both side by side. 3. In the MainGameController, set the flag `story` to false, `demo` to false", - and `socialStories` to true. + and `socialStories` to true. 4. Build and deploy. ## Bugs and Known Issues @@ -356,9 +356,6 @@ page](https://github.com/personal-robots/SAR-opal-base/issues). - Move 'highlight' object with transformer, currently does not follow drag path very well - Log all log messages locally to tablet -- Objects can leave the viewable screen on drag, changes margins (this is - because we used TouchScript's Transformer for drag, which doesn't have the - margins for where not to go) - Add some way of easily seeing which objects in the scene are draggable or able to be interacted with; used to do the grow-shrink pulse motion, but that caused havoc with the collision detection @@ -370,9 +367,6 @@ page](https://github.com/personal-robots/SAR-opal-base/issues). screen without specifying exact coordinates. Adjust the loaded image's position until no collisions are detected so it does not overlap with other images. -- Adapt all games to different screen sizes (currently size of images in most - games is hard-coded, not scaling - social stories game is the only one that - scales properly) - Look into ROS .NET for C#. Possible replacement for websocket connection? - Add demo and story options to config file so you don't have to recompile and redeploy (and/or a start screen that lets you pick whether you want demo diff --git a/sar-opal-base/Assets/scripts/MainGameController.cs b/sar-opal-base/Assets/scripts/MainGameController.cs index 425b4c9..7d75dc8 100644 --- a/sar-opal-base/Assets/scripts/MainGameController.cs +++ b/sar-opal-base/Assets/scripts/MainGameController.cs @@ -75,6 +75,10 @@ public class MainGameController : MonoBehaviour // fader for fading out the screen private GameObject fader = null; + // This flag indicates whether we should scale graphics based on the + // screen height (if true) or width (if false). + private bool scaleToHeight = false; + // config private GameConfig gameConfig; @@ -89,7 +93,11 @@ void Awake() if (this.socialStories) Logger.Log("--- RUNNING IN SOCIAL STORIES MODE ---"); string path = ""; - + + // Scale all camera views to match the screen size of whatever + // device we're running on. + this.scaleToHeight = Utilities.ScaleCameraViewToScreen(); + // find the config file #if UNITY_ANDROID path = Constants.CONFIG_PATH_ANDROID + Constants.OPAL_CONFIG; @@ -407,7 +415,8 @@ public void InstantiatePlayObject (PlayObjectProperties pops, Sprite spri) // set object name go.name = (pops.Name() != "") ? Path.GetFileNameWithoutExtension(pops.Name()) : UnityEngine.Random.value.ToString(); - Logger.Log("Creating new play object: " + pops.Name()); + Logger.Log("Creating new play object: " + pops.Name() + ", with properties: " + + pops.ToString()); // set layer based on whether the object is draggable or not go.layer = (pops.draggable ? Constants.LAYER_MOVEABLES : Constants.LAYER_STATICS); @@ -1459,7 +1468,7 @@ private void ToggleCorrect(bool show) /// Number of answer options for this story public void SetupSocialStoryScene(int numScenes, bool scenesInOrder, int numAnswers) { - // check that we got valid data first + // Check that we got valid data first. if (numScenes < 1) { Logger.LogWarning("Setup Social Story Scene: Told to set up fewer " + @@ -1467,42 +1476,67 @@ public void SetupSocialStoryScene(int numScenes, bool scenesInOrder, int numAnsw return; } - // save whether we are showing a social story in order or not in order + // Save whether we are showing a social story in order or not in + // order, since we will need that information later. this.scenesInOrder = scenesInOrder; - - // set up camera sizes so the viewport is the size of the screen - // TODO move to MainGameController, adapt all scaling etc throughout to scale - // propertly for screen size.... not just for social story games - foreach (Camera c in Camera.allCameras) - { - c.orthographicSize = Screen.height/2; - } - - // load background image + + // We have already scaled the cameras so the camera viewport is at + // at 16:9 aspect ratio that fits within the device's screen. So + // here, we should use the camera viewport size, not the screen + // size, to determine how to scale the loaded graphics. This is + // because the device screen may not have a 16:9 aspect ratio, so + // there may be empty/black bars on either side of the game scene or + // at the top and bottom, depending on the screen's aspect ratio. We + // don't want any graphics to be shown outside the camera view. + + // Load background image. Logger.Log ("Loading background"); Sprite bk = Resources.Load(Constants.GRAPHICS_FILE_PATH + "SSBackground"); BackgroundObjectProperties bops = new BackgroundObjectProperties( "SSBackground", Constants.TAG_BACKGROUND, - // scale background to size of screen - new Vector3((float) Screen.width / bk.bounds.size.x, - (float)Screen.width / bk.bounds.size.x, - (float)Screen.width / bk.bounds.size.x)); + // Scale background to size of camera view. + Camera.main.ScreenToWorldPoint(new Vector3( + (float)(this.scaleToHeight ? Camera.main.pixelHeight / bk.bounds.size.y + : Camera.main.pixelWidth / bk.bounds.size.x), + (float)(this.scaleToHeight ? Camera.main.pixelHeight / bk.bounds.size.y + : Camera.main.pixelWidth / bk.bounds.size.x), + (float)(this.scaleToHeight ? Camera.main.pixelHeight / bk.bounds.size.y + : Camera.main.pixelWidth / bk.bounds.size.x)))); this.InstantiateBackground(bops, bk); - - // need to scale scene/answer slots to evenly fit in the screen - // scene slots are wider than answer slots - 16:9 ratio - // answer slots are square - // - // they can be bigger if there are fewer slots - // but never make them taller than two-fifths the screen height - float slotwidth = (float) (Screen.width / numScenes * 0.85); - if ((slotwidth * 9/16) > Screen.height / 2) slotwidth = (float) (Screen.height / 2); - // save slot width so we can load scenes of the right size later + + // We need to scale the scene and answer slots to evenly fit in the + // screen. We'll use the background image in place of the actual + // screen. The scene slots are wider than answer slots and have a + // 16:9 aspect ratio. The answer slots are square. + // Slots can be bigger if there are fewer slots, but we never want + // them to be taller than two-fifths of the screen height. + float backgroundWidth = GameObject.FindGameObjectWithTag( + Constants.TAG_BACKGROUND).GetComponent().bounds.size.x; + float backgroundHeight = GameObject.FindGameObjectWithTag( + Constants.TAG_BACKGROUND).GetComponent().bounds.size.y; + float slotwidth = backgroundWidth / numScenes * 0.88f; + + Logger.Log("Background size = (" + backgroundWidth + ", " + backgroundHeight + ")"); + Logger.Log("The " + numScenes + " slot(s) will have width " + slotwidth); + + // If the height of the slots is greater than half the camera view + // height, shrink the slots so they fit on the top half of the + // screen. + if ((slotwidth * 9/16) > (backgroundHeight / 2)) + { + Logger.Log("Slots would be too tall based on the width " + slotwidth + + " so we're shrinking them..."); + slotwidth = (backgroundHeight / 2) * 16/9.0f * 0.95f; + Logger.Log("New slot width is " + slotwidth); + } + + // Save slot width so we can load scenes of the right size later. this.slotWidth = slotwidth; - // get answer slot width later, if we need to load answer slots + // We get the answer slot width later, since we do not always need + // to load answer slots. - // load the number of slots needed for this story + // Load the number of slots needed for this story. for (int i = 0; i < numScenes; i++) { Sprite s = Resources.Load(Constants.GRAPHICS_FILE_PATH @@ -1515,30 +1549,44 @@ public void SetupSocialStoryScene(int numScenes, bool scenesInOrder, int numAnsw Logger.LogError("Could not load scene slot image!" ); continue; } - + + Logger.Log("The x-position of next slot will be: " + + ((-backgroundWidth / 2) + (backgroundWidth / (numScenes * 2)) + + (i * (backgroundWidth / numScenes)))); + PlayObjectProperties pops = new PlayObjectProperties( - Constants.SCENE_SLOT + i, // name - Constants.TAG_PLAY_OBJECT, // tag - false, // draggable - null, // audio + // Slot name is just the name plus a counter. + Constants.SCENE_SLOT + i, + // Tag the slot as a play object so we handle it properly + // elsewhere. + Constants.TAG_PLAY_OBJECT, + // Slots are not draggable. + false, + // Slots have no audio attached. + null, + // Position the object: + // x = left edge + offset to first item + + // (counter * width of one item without item margins) + // y = near top of screen + // z = specified in constants new Vector3 ( - // left edge + offset to first item + counter * width/count - (-Screen.width/2) - + (Screen.width / (numScenes * 2)) - + (i * Screen.width / (numScenes)), - // near top of screen - Screen.height * 0.25f, Constants.Z_SLOT), - // scale slot to one portion of the screen width - new Vector3(slotwidth / s.bounds.size.x, - (slotwidth * 9/16) / s.bounds.size.y, - slotwidth / s.bounds.size.z) + (-backgroundWidth / 2) + + (backgroundWidth / (numScenes * 2)) + + (i * (backgroundWidth / numScenes)), + backgroundHeight * 0.25f, + Constants.Z_SLOT), + // Scale slot to one portion of the screen width. + new Vector3( + slotwidth / s.bounds.size.x, + (slotwidth * 9/16) / s.bounds.size.y, + slotwidth / s.bounds.size.z) ); - // instantiate the scene slot + // Instantiate the scene slot with the properties above. this.InstantiatePlayObject(pops, s); - // change name and size to make smaller version that we will use - // to detect collisions during out-of-order games + // Change name and size to make smaller version that we will use + // to detect collisions during out-of-order games. if (!scenesInOrder) { pops.SetName(Constants.SCENE_COLLIDE_SLOT + i); @@ -1549,29 +1597,39 @@ public void SetupSocialStoryScene(int numScenes, bool scenesInOrder, int numAnsw pops.Scale().y / 3, pops.Scale().z) / 3); - // instantiate smaller scene collision object + // Instantiate smaller scene collision object. this.InstantiatePlayObject(pops, s); } } - // load answer slots, if we need to + // Load answer slots, if we need to. if (numAnswers >= 1) { - // answer slot width - float aslotwidth = (float) (Screen.width / numAnswers * 0.8); - if (aslotwidth > Screen.height * 2/5) aslotwidth = (float) (Screen.height * 2/5); - // save answer slot width so we can load answers of the right size later + // Calculate answer slot width so they're evenly spaced. + float aslotwidth = (float) (backgroundWidth / numAnswers * 0.8); + + // If the height of the slots is greater than two-fifths of the + // camera view height, shrink the slots so they fit in the + // bottom half of the screen. + if (aslotwidth > backgroundHeight * 2/5) + { + aslotwidth = backgroundHeight * 2/5; + } + + // Save answer slot width so we can load answer graphics of the + // right size later. this.answerSlotWidth = aslotwidth; - // find the image files for the scenes - // load the number of answer slots needed for this story - // all answer slots look the same so load one graphic and reuse it + // Find the image files for the answer slots and load the number + // of answer slots needed for this story. Since all the answer + // slots look the same, we can load one graphic and reuse it. Sprite ans = Resources.Load(Constants.GRAPHICS_FILE_PATH + Constants.SOCIAL_STORY_FILE_PATH + Constants.SS_ANSWER_SLOT_PATH + Constants.SS_SLOT_NAME); - // also load graphics for correct and incorrect feedback + // We also load graphics for correct and incorrect feedback, + // since these will be shown over the answer slots on request. Sprite feedc = Resources.Load(Constants.GRAPHICS_FILE_PATH + Constants.SOCIAL_STORY_FILE_PATH + Constants.SS_FEEDBACK_PATH @@ -1584,52 +1642,68 @@ public void SetupSocialStoryScene(int numScenes, bool scenesInOrder, int numAnsw for (int i = 0; i < numAnswers; i++) { - // create answer slot + // Create answer slot properties. PlayObjectProperties pops = new PlayObjectProperties( - Constants.ANSWER_SLOT + i, // name - Constants.TAG_ANSWER_SLOT, // tag - false, // draggable - null, // audio + // Slot name is just the name plus a counter. + Constants.ANSWER_SLOT + i, + // Tag the slot as a play object so we handle it properly + // elsewhere. + Constants.TAG_ANSWER_SLOT, + // Slots are not draggable. + false, + // Slots have no audio attached. + null, + // Position the object: + // x = left edge + offset to first item + + // (counter * width of one item without item margins) + // y = near bottom of screen + // z = specified in constants new Vector3 ( - // left edge + offset to first item + counter * width/count - (-Screen.width/2) - + (Screen.width / (numAnswers * 2)) - + (i * Screen.width / (numAnswers)), - // near botton of screen - -Screen.height * 0.25f, Constants.Z_SLOT), - // scale to one portion of the screen width - new Vector3(slotwidth / ans.bounds.size.x, - slotwidth / ans.bounds.size.x, - slotwidth / ans.bounds.size.x) + (-backgroundWidth / 2) + + (backgroundWidth / (numAnswers * 2)) + + (i * backgroundWidth / (numAnswers)), + -backgroundHeight * 0.25f, + Constants.Z_SLOT), + // Scale to one portion of the screen width. + new Vector3( + aslotwidth / ans.bounds.size.x, + aslotwidth / ans.bounds.size.x, + aslotwidth / ans.bounds.size.x) ); - // instantiate the scene slot + // Instantiate the answer slot. this.InstantiatePlayObject(pops, ans); - // also load answer feedback graphics for answer slots - // we know only one answer will be correct, so load 1 correct, x incorrect - // like with the highlight, keep reference to the answer feedback graphics - // but set them as not visible + // Load answer feedback graphics for each answer slots. We + // know only one answer will be correct, so load 1 correct + // and N incorrect (where N is the number of answer slots + // minus one). Like with the highlight object, keep a + // reference to each feedback graphic and set them as not + // visible. PlayObjectProperties pobps = new PlayObjectProperties( - (i < numAnswers - 1 ? "feedback-incorrect" + i : "feedback-correct"), // name + // Put "feedback" in the names. + (i < numAnswers - 1 ? "feedback-incorrect" + i : "feedback-correct"), (i < numAnswers - 1 ? Constants.TAG_INCORRECT_FEEDBACK : - Constants.TAG_CORRECT_FEEDBACK), // tag - false, // draggable - null, // audio - new Vector3 ( - // left edge + offset to first item + counter * width/count - (-Screen.width/2) - + (Screen.width / (numAnswers * 2)) - + (i * Screen.width / (numAnswers)), - // near botton of screen - -Screen.height * 0.25f, Constants.Z_FEEDBACK), - // scale to one portion of the screen width - new Vector3(aslotwidth / (i < numAnswers - 1 ? feedic : feedc).bounds.size.x * 1.2f, - aslotwidth / (i < numAnswers - 1 ? feedic : feedc).bounds.size.x * 1.2f, - aslotwidth / (i < numAnswers - 1 ? feedic : feedc).bounds.size.x * 1.2f) + // Tas as feedback. + Constants.TAG_CORRECT_FEEDBACK), + // Feedback graphics are not draggable. + false, + // Feedback graphics have no audio attached. + null, + // Position on top of the corresponding answer slot. + pops.InitPosition(), + // Scale to one portion of the screen width, slightly + // bigger than the answer slot beneath it. + new Vector3( + aslotwidth / + (i < numAnswers - 1 ? feedic : feedc).bounds.size.x * 1.2f, + aslotwidth / + (i < numAnswers - 1 ? feedic : feedc).bounds.size.x * 1.2f, + aslotwidth / + (i < numAnswers - 1 ? feedic : feedc).bounds.size.x * 1.2f) ); - // instantiate the scene slot + // Instantiate the feedback graphic. this.InstantiatePlayObject(pobps, (i < numAnswers - 1 ? feedic : feedc)); } } diff --git a/sar-opal-base/Assets/scripts/PlayObjectProperties.cs b/sar-opal-base/Assets/scripts/PlayObjectProperties.cs index d2fca82..cbdf004 100644 --- a/sar-opal-base/Assets/scripts/PlayObjectProperties.cs +++ b/sar-opal-base/Assets/scripts/PlayObjectProperties.cs @@ -332,5 +332,28 @@ public int CorrectSlot() { return this.correctSlot; } + + /// + /// Returns a that represents the current + /// . + /// + /// A that represents the current + /// . + public override string ToString () + { + return "Properties:" + + "\n\tName: " + this.Name() + + "\n\tTag: " + this.Tag() + + "\n\tDraggable: " + this.draggable + + "\n\tAudio: " + this.audioFile + + "\n\tInitPosn: " + this.initPosn + + "\n\tScale: " + this.scale + + "\n\tSlot: " + this.slot + + "\n\tCorrectSlot: " + this.correctSlot + + "\n\tIsAnswerSlot: " + this.isAnswerSlot + + "\n\tIsCorrect: " + this.isCorrect + + "\n\tIsIncorrect: " + this.isIncorrect; + + } } } \ No newline at end of file diff --git a/sar-opal-base/Assets/scripts/SceneObjectProperties.cs b/sar-opal-base/Assets/scripts/SceneObjectProperties.cs index 1ba2db4..83eb16b 100644 --- a/sar-opal-base/Assets/scripts/SceneObjectProperties.cs +++ b/sar-opal-base/Assets/scripts/SceneObjectProperties.cs @@ -85,8 +85,15 @@ public string Tag () * position is within the screen */ public void SetInitPosition (Vector3 posn) { - - this.initPosn = this.CheckOnScreen(posn); + + // Since we are scaling graphics to fit on the screen, the screen + // size may not correspond to the camera view, depending on the + // screen's aspect ratio. So this check to make sure new objects are + // on the screen may not actually work to keep objects on screen, + // and furthermore, may give the wrong answer in terms of screen + // coordinates vs. world coordinates. TODO: revisit this and fix. + //this.initPosn = this.CheckOnScreen(posn); + this.initPosn = posn; } /** get object initial position */ diff --git a/sar-opal-base/Assets/scripts/Utilities.cs b/sar-opal-base/Assets/scripts/Utilities.cs index 7d1e99a..6968baa 100644 --- a/sar-opal-base/Assets/scripts/Utilities.cs +++ b/sar-opal-base/Assets/scripts/Utilities.cs @@ -152,7 +152,8 @@ public static Sprite LoadSpriteFromFile(string filepath) } catch (Exception e) { - Logger.LogError("Could not load image from file: " + filepath + "\nError: " + e.Message + Logger.LogError("Could not load image from file: " + filepath + + "\nError: " + e.Message + e.StackTrace); } @@ -161,5 +162,93 @@ public static Sprite LoadSpriteFromFile(string filepath) // not sure why - need to look into this } + + /// + /// Scale all camera views to the current screen size. + /// + /// True if we should scale to the height (i.e., aspect ratio + /// of the screen is wider than it is tall, so we are using the full + /// screen height), false if we should scale to the width (i.e., we are + /// using the full screen width). + public static bool ScaleCameraViewToScreen() + { + // The target aspect ratio for the game is 16:9. If the screen has + // a different default aspect ratio than this, we will show the game + // with empty/black bars filling the extra space on the top or sides + // (so the game is shown in the desired aspect ratio). The reason we + // set the aspect ratio for our game is because the graphics we are + // using fit best at that ratio. + float targetAspectRatio = 16.0f / 9.0f; + + // Determine the game screen's current aspect ratio. + float currentAspectRatio = (float)Screen.width / (float)Screen.height; + + // We want the game to use the target aspect ratio, so we will scale + // the camera's viewport height so the camera view's aspect ratio + // matches the target aspect ratio. + float scaleHeight = currentAspectRatio / targetAspectRatio; + + // However, we also want the entire game scene to be viewable on + // screen. So we have to check whether scaling the height to the + // target aspect ratio would be taller than would fit on the device + // screen, and if so, shrink the camera view to fit on the device + // screen. We create a new rectangle that will be the camera's view + // rectangle of the desired size and aspect ratio by scaling the + // current camera view rectangle. + // We scale the view for each camera we have. + foreach (Camera c in Camera.allCameras) + { + Rect rect = c.rect; + if (scaleHeight < 1.0f) + { + // The height to scale to is smaller than the current + // screen height , so we use that height, set the + // corresponding width, and set the top left coordinate of + // where to put the camera rectangle on the screen. This is + // how we get evenly distributed empty/black bars above and + // below the game window. + rect.width = 1.0f; + rect.height = scaleHeight; + rect.x = 0; + rect.y = (1.0f - scaleHeight) / 2.0f; + // Now that we have our rectangle, change the camera view. + c.rect = rect; + // We return whether or not we should scale to the height-- + // that is, is the aspect ratio of the screen wider than it + // is tall, and thus are we using the full screen height? + // Otherwise, the screen is taller than it is wide, so we + // are using the full screen width instead. + return false; + } + else + { + // Otherwise, the height to scale to is bigger than the + // current screen height, so we set the height as tall as + // we can and scale the width to be smaller and fit too. In + // this case, we set the top left coordinate such that we + // have empty/black bars to the left and right of the game + // window. + rect.width = (1.0f / scaleHeight); + rect.height = 1.0f; + rect.x = (1.0f - (1.0f / scaleHeight)) / 2.0f; + rect.y = 0; + // Now that we have our rectangle, change the camera view. + c.rect = rect; + // Return true to indicate that we should scale based on the + // height, since we are using the full screen height. + return true; + // Note that we could also set the orthographic size of the + // camera (which is half the vertical height of the view) to + // be the half the height of the screen. But this would not + // necessarily keep our desired aspect ratio, since Unity + // would adjust the horizontal size based on the viewport's + // aspect ratio, which is not necessarily something we + // control. + } + } + // Default return something, but we will always have a camera, so we + // should never get here. + return true; + } } }