/// <summary> /// Turns items around his position clockwise. Then destroys itself. /// /// It takes care of all the changes by itself. So no popping outside. /// Only physics needs to get used. /// </summary> /// <param name="pos">Position in the level</param> /// <param name="updateOnly">UpdateOnly always true</param> /// <returns>Array of item position to update</returns> public override IntVector2[] Activate(IntVector2 pos, out bool updateOnly) { List<IntVector2> back = new List<IntVector2>(); updateOnly = true; back.Add(pos); IntVector2 levelSize = context.GetSize(); //Test if the position is in bounce. If the turn to near to the left, right top or ground //it only pops itself. if( pos.x != 0 && pos.y != 0 && pos.x != levelSize.x-1 && pos.y != levelSize.y-1 ){ //save the first item IntVector2 current = pos + items[0]; Gem temp = context.GetItem(current); //swap all items clockwise. IntVector2 last = current; for(int i = items.Length -1; i > 0; i--){ current = pos + items[i]; context.SetItem(last, context.GetItem(current)); back.Add(last); last = current; } //set the first item on the position of the last context.SetItem(last,temp); back.Add(last); } //remove turn from the level context.SetItem(pos,null); return back.ToArray(); }
/// <summary> /// Bring all gems down. /// </summary> /// <param name="updateHorizontalArray">Update horizontal array.</param> private GemMove[] FallingDown(int[] updateHorizontalArray) { int width = updateHorizontalArray.Length; List<IntVector2> fallingBlocksStart = new List<IntVector2>(); List<IntVector2> fallingBlocksSEnd = new List<IntVector2>(); for(int x = 0; x < width; x++){ bool firstRun = true; if(updateHorizontalArray[x] !=-1){ // this column needs an update int currentY = updateHorizontalArray[x]; bool somethingToMove = true; while(currentY > -1 && somethingToMove){ somethingToMove = false; //if there is a item now we can start one item up if(level.GetItem(new IntVector2(x,currentY)) != null){ currentY--; } // if there is space bring them all one down for(int y = currentY-1; y >= 0; y--){ IntVector2 currentXY = new IntVector2(x,y+1); IntVector2 currentXYTop = new IntVector2(x,y); if(level.GetItem(currentXY) == null){ level.SetItem(currentXY, level.GetItem(currentXYTop)); if(level.GetItem(currentXYTop) != null){ if(firstRun){ fallingBlocksStart.Add(currentXYTop); fallingBlocksSEnd.Add(currentXY); }else{ int pos = fallingBlocksSEnd.IndexOf(currentXYTop); if(pos != -1){ fallingBlocksSEnd[pos] = currentXY; }else{ string s = "try to move a not found object?\n"; s += "Look for "+currentXYTop+"\n"; for(int i = 0; i < fallingBlocksSEnd.Count; i++) s+= fallingBlocksSEnd[i].ToString() + ", "; Debug.LogWarning(s); } } } level.SetItem(currentXYTop, null); } } //if there are items over the currentY pos we need to loop more. for(int y = currentY-1; y >= 0 && !somethingToMove; y--){ if(level.GetItem(new IntVector2(x,y+1)) != null) somethingToMove = true; } firstRun = false; } } } GemMove[] fallingMoves = new GemMove[fallingBlocksStart.Count]; for(int i = 0; i < fallingMoves.Length; i++){ fallingMoves[i].from = fallingBlocksStart[i]; fallingMoves[i].to = fallingBlocksSEnd[i]; } return fallingMoves; }
/// <summary> /// Returns an array of items to pop in a cross shape. /// </summary> /// <param name="pos">Position in the level</param> /// <param name="updateOnly">UpdateOnly is always false</param> /// <returns>Array of item position to pop</returns> public override IntVector2[] Activate(IntVector2 pos, out bool updateOnly) { updateOnly = false; IntVector2 leveSize = context.GetSize(); IntVector2[] back = new IntVector2[leveSize.x + leveSize.y -1]; back[0] = pos; int counter = 1; //Insert horizontal, without our own pos. for(int y = 0; y < pos.y; y++){ back[counter++] = new IntVector2(pos.x, y); } for(int y = pos.y+1; y < leveSize.y; y++){ back[counter++] = new IntVector2(pos.x, y); } //Insert vertical, without our own pos. for(int x = 0; x < pos.x; x++){ back[counter++] = new IntVector2(x, pos.y); } for(int x = pos.x+1; x < leveSize.x; x++){ back[counter++] = new IntVector2(x, pos.y); } return back; }
/// <summary> /// Initialize this Class /// </summary> public override void Awake() { base.Awake(); spriteRenderer = GetComponent<SpriteRenderer>(); startPos = new IntVector2((int)transform.position.x, (int)-transform.position.y); targetPos = startPos; }
/// <summary> /// Returns all item positions in the same group. /// </summary> /// <param name="pos">Position in the level</param> /// <param name="updateOnly">UpdateOnly is always false.</param> /// <returns>Array of item position to pop.</returns> public override IntVector2[] Activate(IntVector2 pos, out bool updateOnly) { updateOnly = false; IntVector2[] groupEle = context.GetGroup(base.group); if(groupEle.Length >= context.GetMinimumPopCount()) return groupEle; else return new IntVector2[0]; }
/// <summary> /// Returns an area around the Bomb. /// </summary> /// <param name="pos">Position in the level</param> /// <param name="updateOnly">UpdateOnly is always false</param> /// <returns>Array of item position to pop.</returns> public override IntVector2[] Activate(IntVector2 pos, out bool updateOnly) { updateOnly = false; List<IntVector2> back = new List<IntVector2>(); back.Add(pos); for(int i = 0; i < explosion.Length; i++){ IntVector2 testPos = pos + explosion[i]; if(context.IsInLevelBounce(pos + explosion[i])) back.Add(testPos); } return back.ToArray(); }
void OnGUI() { if(debug != DebugType.OFF){ levelManger.SetPause(true); Vector3 topleft = Camera.main.WorldToScreenPoint(- new Vector3(0.5f,0.5f,0f)); Vector3 buttomRight = Camera.main.WorldToScreenPoint(new Vector3(0.5f,0.5f,0f)); float itemWidth = buttomRight.x - topleft.x; float itemHeight = buttomRight.y - topleft.y; topleft.y = Screen.height - topleft.y - itemHeight; IntVector2 size = level.GetSize(); for(int y = 0; y < size.y; y++){ for(int x = 0; x < size.x; x++){ Gem g = level.GetItem(new IntVector2(x,y)); string text = ""; switch(debug){ case DebugType.ONE_DIM_PLUS_GROUPS: text = new IntVector2(x,y).ToOneDim(size.x) +"\nG:"+ ((g != null)?g.group.ToString():""); break; case DebugType.TOW_DIM_PLUS_GROUPS: text = x+","+y +"\nG:"+ ((g != null)?g.group.ToString():""); break; } if(g != null && selecedGroup == g.group) GUI.backgroundColor = Color.blue; else GUI.backgroundColor = Color.white; if(GUI.Button(new Rect(topleft.x + (x * itemWidth), topleft.y + (y * itemHeight), itemWidth,itemHeight),text)){ Debug.Log(new IntVector2(x,y) + " = " + ((g!=null)?g.ToString():"null")); if(g != null){ if(selecedGroup == g.group){ selecedGroup = -1; }else{ selecedGroup = g.group; } } } } } }else{ levelManger.SetPause(false); } }
/// <summary> /// Get the level from source. /// </summary> /// <param name="level">level</param> /// <param name="levelSize">levelSize of the level x = height y = wigth</param> public void GetLevel(out Gem[] level, out IntVector2 levelSize) { levelSize = new IntVector2(source.width, source.maxLinesOnScreen); level = new Gem[levelSize.x*levelSize.y]; currentLines = new int[levelSize.x]; //calc free lines int start = source.maxLinesOnScreen - source.linesOnStart; //convert in one dim array pos start = start * levelSize.x; int i = 0; for(; i < source.gems.Length && i < level.Length - start; i++){ level[i + start] = GemFactory.GetGem(source.gems[i]); } start = i + 1; for(int j = 0; j < currentLines.Length; j++){ currentLines[j] = source.linesOnStart; } }
/// <summary> /// What should happen if the Player click on the item. /// /// If updateOnly is true after execution then the item edited /// the level by itself. /// Otherwise the context need to pop the positions of the return /// array. /// In both cases the returned array contains the touched items. /// /// For chain reaction types it's important that the first position /// in array are the item itself. Otherwise it is likely to create a /// endless loop. /// </summary> /// <param name="pos">Position in the level</param> /// <param name="updateOnly">UpdateOnly: false = context has to pop. Otherwise item thaking care by itself</param> /// <returns>Array of item position to pop or to update</returns> public abstract IntVector2[] Activate(IntVector2 pos, out bool updateOnly);
/// <summary> /// Generate the level to view on start. /// </summary> /// <param name="level">level</param> /// <param name="levelSize">levelSize of the level x = height y = wigth</param> public void GetLevel(out Gem[] level, out IntVector2 levelSize) { levelSize = new IntVector2 (levelWidth, levelHeight); lineCache = new Gem[levelWidth]; level = new Gem[levelWidth * levelHeight]; for(int y = freeLines; y < levelHeight; y++){ //read in last line Gem[] lastLine = new Gem[levelWidth]; for(int x = 0; x < levelWidth; x++){ lastLine[x] = level[(y-1) * levelWidth + x]; } //refill Cached Line FillLineCache(lastLine); //Fill in a hippo if needed if(y == freeLines && generateTarget){ lineCache[random.Next(0, levelWidth)] = new Target(); } //copy the lineCache in the level and clear it for(int x = 0; x < levelWidth; x++){ level[y * levelWidth + x] = lineCache[x]; lineCache[x] = null; } } }
/// <summary> /// Set a two dimensional level. /// </summary> /// <param name="level">Level.</param> public void SetLevel(Gem[,] level) { bonus = 0; size = new IntVector2(level.GetLength(1), level.GetLength(0)); items = new Gem[size.x * size.y]; int i = 0; for(int y = 0; y < size.y; y++){ for(int x = 0; x < size.x; x++){ items[i++] = level[x,y]; if(level[x,y] != null) level[x,y].Init(this); } } }
/// <summary> /// Set a one dimensional level /// </summary> /// <param name="level">Level</param> /// <param name="size">Size of the level</param> public void SetLevel(Gem[] level, IntVector2 size) { bonus = 0; this.size = size; this.items = level; for(int i = 0; i < level.Length; i++){ if(level[i] != null) level[i].Init(this); } }
/// <summary> /// Determines if this position is in the borders of this level. /// </summary> /// <returns>true</returns> /// <c>false</c> /// <param name="i">The index.</param> public bool IsInLevelBounce(IntVector2 i) { return i.x >= 0 && i.y >= 0 && i.x < size.x && i.y < size.y; }
public void SetItem(IntVector2 pos, Gem g) { SetItem(pos.ToOneDim(size.x),g); }
/// <summary> /// Manhattan Distance calculated faster then Euclidean distance. /// Also we expect Gems moving on the grid and not move /// straight through the playing field. /// </summary> /// <param name="a">start pos</param> /// <param name="b">end pos</param> /// <returns>distance between them (number of fields to move)</returns> private int ManhattanDistance(IntVector2 a, IntVector2 b) { return Mathf.Abs(a.x-b.x)+ Mathf.Abs(a.y-b.y); }
/// <summary> /// Gets the item on x and y. /// </summary> /// <returns>The item.</returns> /// <param name="pos">Position in x and y.</param> public Gem GetItem(IntVector2 pos) { return GetItem(pos.ToOneDim(size.x)); }
/// <summary> /// Play movement animation. /// Move Item from "current" to "target" in the time of "duration". /// This will simply push the GemVisuals around. It will nor update the element at the target position. /// </summary> /// <param name="current">position of the Gem that should move</param> /// <param name="target">position where is should move to</param> /// <param name="duration">The Time it has to execute the move</param> public void PlayGemAnimation(IntVector2 current, IntVector2 target, float duration) { visuals[current.x,current.y].MoveTo(target, duration); }
/// <summary> /// Handle when a player hit a gem to activate it. /// </summary> /// <returns><c>true</c>, when something has changed, <c>false</c> if not for example Player hits a free space.</returns> /// <param name="hitPos">Hit position.</param> private bool ExecuteHit(IntVector2 hitPos) { Gem g = level.GetItem(hitPos); if(g != null && g.IsPopabel()){ bool updateOnly; IntVector2[] affected = g.Activate(hitPos, out updateOnly); if(affected.Length > 0){ if(updateOnly){ //The item has taken care of the level by themself update only for(int i = 0; i < affected.Length; i++){ Gem current = level.GetItem(affected[i]); visuals.SetGem(affected[i], current); if(current == null && updateHorizontalArray[affected[i].x] < affected[i].y){ updateHorizontalArray[affected[i].x] = affected[i].y; } } }else{ //The item returns a list of other items to pop. for(int i = 0; i < affected.Length; i++){ Gem toPop = level.GetItem(affected[i]); if(toPop != null){ if(toPop.chain){ if(hitPos == affected[i]){ level.SetItem(affected[i], null); }else{ //in a chain reaction we recursive call this method. //this is the reason why the item must be the first in the line. //so it's consumed before some other item chain back in an endless loop. ExecuteHit(affected[i]); } } if(toPop.Pop()){ level.SetItem(affected[i], null); } } //save the lowest affected y position for the "physic engine" if(updateHorizontalArray[affected[i].x] < affected[i].y){ updateHorizontalArray[affected[i].x] = affected[i].y; } } } return true; } } return false; }
/// <summary> /// Recreate the UI /// </summary> /// <param name="size">Size.</param> public void Recreate(IntVector2 size) { DestroyLevel(); CreateLevel(size); }
/// <summary> /// Sets the gem visuals. /// </summary> /// <param name="pos">Position.</param> /// <param name="g">The Gem item.</param> public void SetGem(IntVector2 pos, Gem g) { visuals[pos.x,pos.y].Show(g); }
/// <summary> /// Simulate a movement to target position. In a specific time. /// </summary> /// <param name="targetPos">target position</param> /// <param name="animationDuration">time to complete the movement</param> public void MoveTo(IntVector2 targetPos, float animationDuration) { this.targetPos = targetPos; animationEndTime = Time.time + animationDuration; this.animationDuration = animationDuration; }
/// <summary> /// Creates the level. /// </summary> /// <param name="size">Size.</param> private void CreateLevel(IntVector2 size) { visuals = new IGemVisual[size.x,size.y+1]; for(int y = 0; y < size.y+1; y++){ for(int x = 0; x < size.x; x++){ GameObject c = (GameObject)GameObject.Instantiate(gemVisual,new Vector3(x,-y,0),Quaternion.identity); c.transform.parent = transform; visuals[x,y] = c.GetComponent<IGemVisual>(); } } //Create an set the position of the new line switch Visual GameObject s = GameObject.Instantiate(switchVisual); switchV = (IHitable)s.GetComponent(typeof (IHitable)); s.transform.position = new Vector3(-2,-size.y+0.5f,0); switchV.hitEvent += delegate(int x, int y) { if(newLineEvent != null) newLineEvent.Invoke();}; //Set the cam on the center of the level Vector3 center = new Vector3(size.x / 2, - (size.y / 2), -10); gameCam.transform.position = center; //Change the size of the camera to fit level Vector3 topleft = - new Vector3(0.5f,-0.5f,0f); topleft = gameCam.WorldToViewportPoint(topleft); float maxV = Mathf.Max (topleft.x, topleft.y); gameCam.orthographicSize *= maxV + 0.125f; //Change the size of the time bars so they fit over the complite last line. timeIndicator.transform.position = new Vector3(-0.5f,-size.y,1); timeIndicator.GetChild(0).transform.localScale = new Vector3(size.x / 2 * 100,50,1); timeIndicator.GetChild(0).transform.localPosition = new Vector3(size.x / 2f,0,-5); timeIndicatorShadow.transform.localScale = new Vector3(size.x / 2 * 100,50,1); timeIndicatorShadow.transform.position = new Vector3(size.x / 2f - 0.5f,-size.y,-4); }
/// <summary> /// This method is called every frame. /// It's reasonable for the movement animation. /// if the target position is not same as the initial position /// we will lerp between initial an target position depending on the animationDuration. /// </summary> public override void Update() { base.Update(); if(targetPos != startPos){ float lerpPos = animationEndTime - Time.time; lerpPos /= animationDuration; if(lerpPos >= 0){ Vector3 pos = Vector3.Lerp(new Vector3(targetPos.x,-targetPos.y,0), new Vector3(startPos.x,-startPos.y,0),lerpPos); transform.position = pos; }else{ targetPos = startPos; } } }
public GemMove(IntVector2 from, IntVector2 to){ this.from = from; this.to = to; }
/// <summary> /// Falling to the right. /// </summary> GemMove[] FallingRight() { IntVector2 size = level.GetSize(); List<int> startXGround = new List<int>(); List<int> endXGround = new List<int>(); List<GemMove> moves = new List<GemMove>(); //find large holes for(int x = 0; x < size.x; x++){ //find the first empty space if(level.GetItem(new IntVector2(x,size.y-1)) == null){ startXGround.Add(x); //if the next line is also empty we will move over it for(int tx = x; tx < size.x; tx++){ if(level.GetItem(new IntVector2(tx,size.y-1)) == null){ x++; }else{ endXGround.Add(x); break; } } } } if(endXGround.Count < startXGround.Count){ endXGround.Add(size.x); } //string s = "list"; //for(int i = 0; i < startXGround.Count; i++){ // s += "("+(startXGround[i]-1) + "," +(endXGround[i]-1)+")"; //} //Debug.Log(s); int targetX = size.x-1; int distance = 1; while(targetX-distance >= 0 && targetX >= 0){ //if we know we have a hole we add this to move distance if(endXGround.Contains(targetX)){ int index = endXGround.IndexOf(targetX); distance += (targetX - startXGround[index]) - 1; } if(level.GetItem(new IntVector2(targetX,size.y-1)) == null){ //the target pos could also be empty => then we have to move the item even more. while(targetX-distance >= 0 && level.GetItem(new IntVector2(targetX-distance,size.y-1)) == null){ distance += 1; } if(targetX-distance >= 0){ for(int y = 0; y < size.y; y++){ IntVector2 from = new IntVector2(targetX-distance,y); IntVector2 to = new IntVector2(targetX,y); Gem currentGem = level.GetItem(from); if(currentGem != null){ moves.Add(new GemMove(from, to)); level.SetItem(to, currentGem); level.SetItem(from, null); } } } } targetX--; } return moves.ToArray(); }