Difference between revisions of "IS480 Team wiki: 2012T1 6P Development"
Cw.lim.2009 (talk | contribs) |
Cw.lim.2009 (talk | contribs) |
||
Line 375: | Line 375: | ||
ob.rigidbody.constraints = RigidbodyConstraints.FreezePositionX|RigidbodyConstraints.FreezePositionY|RigidbodyConstraints.FreezePositionZ|RigidbodyConstraints.FreezeRotationZ; | ob.rigidbody.constraints = RigidbodyConstraints.FreezePositionX|RigidbodyConstraints.FreezePositionY|RigidbodyConstraints.FreezePositionZ|RigidbodyConstraints.FreezeRotationZ; | ||
ob.gameObject.active = true; | ob.gameObject.active = true; | ||
− | + | } | |
====Amplifying effects of control==== | ====Amplifying effects of control==== | ||
</div> | </div> |
Revision as of 23:49, 27 November 2012
Home | Project Overview | Project Management | Project Documentation | Meeting Minutes | Game Development |
Overview | Game Mechanics | User Testing | UI Screenshots | Graphics Development | Technical Complexity |
Contents
Technical Complexity
Game Scenes Setup
Sprite collections with polygon colliders setup
Every images before appearing on the scene has to be added to a sprite collection library where its precise settings and parameters are manually set in order for the image to take shape in the scene and allow it to have natural interaction with other objects.
Scene setup
The following is an example of a scene setup that consists of 16 different objects laid out in one frame of the scene. The average amount of objects in one stage would go up to about 400 and the crucial part of making the game fun and challenging greatly relies on the precise manual positioning of every objects in this step.
Precise control of objects behaviour
The main focus of the development work of our project is to achieve precise control over the manipulation of the components' behaviour that are attached to all objects with the use of C# scripts, this includes collision and effects handling for every single event.
With the use of C# scripts, we are able to have precise control on every collision events and the desired outcome of each case:
- Gralch triggering a trap at the precise contact point.
- Gralch gains x amount of energy upon contact with y type of cherry.
- Gralch loses x amount of energy upon contact with y type of obstacle.
- Gralch passing a checkpoint which result to a notification to user.
- Gralch movements along the game
- Elevates upon touch and hold of mobile input on touchscreen
- Freefall on release of touch of mobile input on touchscreen
- Gralch animations
- Idle - During start of game
- Flying - Upon exiting contact with platform or planks
- Running - Upon contact with platform or planks
- Constant draining of Gralch's energy throughout game.
- Gradual speed increase of Gralch throughout the stage as player progresses to further checkpoints.
Game Controls
Pseudo Code For Touch & Hold
IF( Mobile Touch Input ) IF( GameHasStarted ) play flying animation button pressed is true flying is true ELSE set rigidbody kinematic to false IF(Audio not mute)Start play audio GameHasStarted is true flying is true play flying animation add force to rigidbody IF(button pressed is true) if(current animation is "Running") play flying animation add force to rigid body
Pseudo Code For Swipe
IF (Input.GetButtonDown) initialize flickTime to 0 get the current pixel position where the input is detected and store as posDown take note of the time when input is detected and store as timeDown
IF (Input.GetButtonUp) get the current pixel position where the input is last detected posUp take note of the time when input is last detected and store as timeUp update flickTime to be the difference between timeUp and timeDown update flickLength to be the distance between the posUp and posDown calculate flickSpeed by having distance, flickLength divide by time, flickTime IF (flickSpeed is beyond limit, flickSpeedCap) set flickSpeed to flickSpeedCap convert both pixel positions, posDown and posUp to the world position, vectorDown and vectorUp
Calculate force to add to the object using this formula - (vectorDown - vectorUp) x flickSpeed Add rigid body force to the object’s ForceMode property
Pseudo Code For Joystick
//Initialization Method Initialize vector position of touchArea to be the bottom left of the screen Get the center position of the touchArea to be touchAreaCenter (x + width/2, y + height/2)
//Linear Interpolation Method for the movement of joystick object Creates an animation that smoothly interpolates through a range of data points Takes in two data points, (xMin,yMin) and (xMax,yMax) xVar in this case is the x position of the joystick object Formula: y = yMin + (yMax – yMin) * ((xVar - xMin) / (xMax – xMin))
//Method to move joystick Get the current pixel position where the input is detected within the touchArea to be posCurr Get the difference in length between posCurr and touchAreaCenter and store it as posDiff
IF (Input.GetButtonDown) IF(posDiff is larger than the joystickRadius) lock joystick.x and joystick.y at the joystick’s circumference (joystickRadius * PI * 2) Convert joystick pos (-1 to be left (yMin), 1 to be right (yMax)) using joystick circumference as a limit apply linear interpolation method to get horizontal property vertical property is basically an inverted output of horizontal position ELSE reset joystick.x and joystick.y position to be touchAreaCenter horizontal and vertical properties are equal to 0
Calculate the change in object’s position upDown leftRight Add rigid body force upDown to ForceMode.Velocity property Add rigid body force, leftRight to ForceMode.Acceleration property
Frame rates issues
Use of performance profiler for tracking bottleneck and solving negative effects on frame rates
There are many components of the development that could cause massive frame rate drop issues, and it could be impossible to identify what are the causes while you playtest the game on the deployment device or even on the development machine, here is one example of how the use of performance profiler helps the team to narrow down the cause of frame rate drop. In this example, the massive frame rate drop is due to a set of codes used in the scripts attached to the objects in the scene.
Although, identification of the cause could help to speed up the process of solving the frame rate issues, it does not help to point out the actual bottleneck or inefficiency in the codes or other components, hence there is a need to trial and error and also seek help from the unity community to rectify this problem.
Google Analytics Integration with Unity Android
Our team decided that there is a need to do a large data collection for players' in game progress and playing styles in order to find out patterns or outliers that helps us to improve our game design and eventually the players' overall satisfaction level. We could only collect a limited amount of feedback from the public through email, ratings and feedback. Hence, we decided to do a data collection on a global scale using the free and powerful analytical tool by Google.
However, Google Analytics does not provide any methods or guide that allows data collection and tracking to be done on Unity3D Mobile platforms. Hence, we faced a great deal of difficulty of integrating this service into our project. After much research and experimentation, we managed to stumble upon a piece of custom script that was shared in unity forums by fellow Unity developers and we managed to edit the script accordingly to suit our needs.
Part of the implementation:
string a, b, c, d, e; a = System.DateTime.Now.ToString("MM/dd"); b = System.DateTime.Now.ToString("HH:mm"); c = duration.Seconds.ToString(); d = GlobalVariables.levelCherriesCollected.ToString(); e = Mathf.RoundToInt(tPos.x).ToString(); if (Application.internetReachability == NetworkReachability.ReachableViaLocalAreaNetwork || Application.internetReachability == NetworkReachability.ReachableViaCarrierDataNetwork){ GoogleAnalyticsHelper.Settings("UA-xxxxxxxx-x", "http://analytics.6production.com"); // your id GoogleAnalyticsHelper.LogPage(Application.loadedLevel.ToString()); GoogleAnalyticsHelper.LogEvent("Gralchv38","ProgressReport","GameOver",a+","+b+","+c+","+d+","+e+","+GlobalVariables.getTrapID(),0); }
As you can see from above, we track players' progress, in terms of date and hour playing, duration they played, distance they went each game, number of cherries collected, last trap hit before game over.
Endless World Creation
The Endless World is the finalised game mechanics that the team focuses in the last stage of the project. In order to achieve such an effect of an endless world, there's a need to handle many of the different technical aspects:
- Differing themes - Transition between various scenes
- Differing obstacles - Randomisation of obstacles and cherries position
- Increasing difficulty and interactions - Dynamic obstacles
- Smooth control - Amplifying effects of control
Transition between various scenes
As gralch progresses through the scene, there’ll be a change in theme. However, the change must not be too sudden, or it will disrupt the user’s experience, and the flow of the game. Therefore, our group implemented a gradual change as Gralch progresses through the stages, by controlling the alpha (transparency) of the two backgrounds (overlay). Some additional points about the implementation are as shown below:
1. At the start of every segment (section of the map), the alpha of the theme image (to show) will be 1f, while the other image (theme) will be 0f.
2. As Gralch progresses along the segment, the alpha of the first theme will move towards 0f, while the other image will move towards 1f.
3. When Gralch reaches the next segment of the map, point 1 is repeated, this time with the other theme starting with 1f.
Example Code:
// find out which segment currently in int segmentTravel = (int)((Runner.distanceTraveled % wholeInterval) / (wholeInterval / 4)) + 1; // find out which round currently in int roundTravel = (int)(Runner.distanceTraveled / wholeInterval) + 1; // find out progress within segment float progress = (Runner.distanceTraveled % (wholeInterval / 4)) / (wholeInterval / 4); if (segmentTravel == 1){ if(renderer.name.Contains("bg1")){ renderer.material.color = new Color(renderer.material.color.r,renderer.material.color.g,renderer.material.color.b,1f); } if(renderer.name.Contains("bg2")){ renderer.material.color = new Color(renderer.material.color.r,renderer.material.color.g,renderer.material.color.b,0f); } } if (segmentTravel == 2){ if(renderer.name == "foreground1"){ renderer.material.color = new Color(((1 - progress)/2),renderer.material.color.g,renderer.material.color.b); } if(renderer.name.Contains("bg1")){ renderer.material.color = new Color(renderer.material.color.r,renderer.material.color.g,renderer.material.color.b,1f - progress); } if(renderer.name.Contains("bg2")){ renderer.material.color = new Color(renderer.material.color.r,renderer.material.color.g,renderer.material.color.b,progress); } } if (segmentTravel == 3){ if(renderer.name.Contains("bg1")){ renderer.material.color = new Color(renderer.material.color.r,renderer.material.color.g,renderer.material.color.b,0f); } if(renderer.name.Contains("bg2")){ renderer.material.color = new Color(renderer.material.color.r,renderer.material.color.g,renderer.material.color.b,1f); } } if (segmentTravel == 4){ if(renderer.name == "foreground1"){ renderer.material.color = new Color(((progress)/2),renderer.material.color.g,renderer.material.color.b); } if(renderer.name.Contains("bg1")){ renderer.material.color = new Color(renderer.material.color.r,renderer.material.color.g,renderer.material.color.b,progress); } if(renderer.name.Contains("bg2")){ renderer.material.color = new Color(renderer.material.color.r,renderer.material.color.g,renderer.material.color.b,1f - progress); } }
Randomisation of obstacles and cherries position
Some examples of the different obstacles:
The randomisation algorithm behind that is working behind the scene that changes these obstacles:
void Initialize () { instance = this; obArray = new List<Transform>(); varOffset = 2f; for(int i = 0; i < prefabs.GetLength(0) ; i++){ obArray.Add((Transform)Instantiate(prefabs[i], new Vector3(-10f, prefabs[i].position.y, -100f), Quaternion.identity)); } initialized = true; enabled = true; }
void Update () { if(!initialized){ Initialize(); ObstacleManager.generateObstacle(new Vector3(Runner.tPos.x+varOffset,0f,0f)); return; } //float xPos =0; if(ob2 != null){ Transform currentObj = ob2.FindChild("lastPosIndicator"); if(currentObj.position.x + recycleOffset+ varOffset < Runner.tPos.x){ ObstacleManager.generateObstacle(new Vector3(Runner.tPos.x+varOffset,0f,0f)); } } }
public static void generateObstacle(Vector3 position){ instance.worldPlacement = position; instance.worldPlacement.x += 6f; //Debug.Log("ArrayCount:" +instance.obArray.Count); int index = Random.Range(0,instance.obArray.Count); Transform ob = instance.obArray[index]; for(int i=0;i< ob.childCount;i++){ ob.GetChild(i).gameObject.active = true; ob.GetChild(i).gameObject.SetActiveRecursively(true); } ob.localPosition = new Vector3(instance.worldPlacement.x,ob.position.y,instance.worldPlacement.z); ob.gameObject.active = true; instance.worldPlacement.x += 12f; int index2 = Random.Range(0,instance.obArray.Count); while(index2 == index){ index2 = Random.Range(0,instance.obArray.Count); } instance.ob2 = instance.obArray[index2]; for(int i=0;i< instance.ob2.childCount;i++){ instance.ob2.GetChild(i).gameObject.active = true; instance.ob2.GetChild(i).gameObject.SetActiveRecursively(true); } instance.ob2.localPosition = new Vector3(instance.worldPlacement.x,instance.ob2.position.y,instance.worldPlacement.z); instance.ob2.gameObject.active = true; int onOff = Random.Range(0,2); if(onOff == 1){ DynamicObstacleManager.generateDynamicObstacle(instance.worldPlacement); instance.varOffset = 3f; }
Dynamic obstacles
This is the logic implementation of how the ghost indicator heads out and follows gralch as it flies up and down the world, we have to make use of enumerator and pause function to achieve the effects of the ghost following the gralch up and down as he flies.
public static void showGhost(){ instance.transform.position = new Vector3(Runner.tPos.x + 5.1f, Runner.tPos.y, Runner.tPos.z); instance.gameObject.active = true; FlyingGhost.alive = true; instance.StartCoroutine(instance.rushToGralch()); } IEnumerator rushToGralch(){ yield return new WaitForSeconds(2f); Vector3 currentPos = transform.position; transform.position = new Vector3(transform.position.x + 1f, transform.position.y, transform.position.z); Transform flyingGhost = obArray[0]; flyingGhost.localPosition = currentPos; flyingGhost.gameObject.active=true; gameObject.active = false; }
Here is the logic implementation of how a ghost spirit is generated and flies towards gralch once the position is locked, here we see that we have to play with the rotation and position of of the ghost object to achieve the effect of a flying ghost that launches out.
public static void generateDynamicObstacle(Vector3 position){
Vector3 worldPlacement = position; worldPlacement.x += 10f; int ran = Random.Range(0,instance.obArray.Count); Transform ob = instance.obArray[ran]; for(int i=0;i< ob.childCount;i++){ ob.GetChild(i).gameObject.active = true; ob.GetChild(i).gameObject.SetActiveRecursively(true); } ob.rigidbody.constraints = RigidbodyConstraints.None; ob.rigidbody.constraints = RigidbodyConstraints.FreezeRotationZ; ob.position = new Vector3(worldPlacement.x,ob.position.y,worldPlacement.z); ob.rigidbody.constraints = RigidbodyConstraints.FreezePositionX|RigidbodyConstraints.FreezePositionY|RigidbodyConstraints.FreezePositionZ|RigidbodyConstraints.FreezeRotationZ; ob.gameObject.active = true; }