public void registerLevel(LevelPoint level) { if (!levels.ContainsKey(level.levelIndex)) { levels.Add(level.levelIndex, level); } }
public Platform(Transform first, Transform second) { First = first; Second = second; FirstPoint = new LevelPoint(first.name); SecondPoint = new LevelPoint(second.name); }
private void GeneratePaths(char[,] worldData) { int count = Random.Range(MinPaths, MaxPaths); LevelPoint[] lastPath = RunPath(LevelWidth / 2, LevelHeigth / 2); if (LevelType == LevelType.Ambush) { StartPoint = lastPath[0]; } applyPath(worldData, lastPath); for (int i = 1; i < count; i++) { LevelPoint start; if (BuildMode == LevelBuildMode.FollowAndContinue) { start = lastPath[Random.Range(0, lastPath.Length)]; } else if (BuildMode == LevelBuildMode.RandomContinue) { start = FloorPoints[Random.Range(0, FloorPoints.Count)]; } else { start.X = LevelWidth / 2; start.Y = LevelHeigth / 2; } LevelPoint[] path = RunPath(start.X, start.Y); applyPath(worldData, path); lastPath = path; } }
private void OnCollisionEnter2D(Collision2D collision) { Transform playerTransform = PlayerController.instance.transform; Collider2D[] hitColliders = Physics2D.OverlapCircleAll(new Vector2(playerTransform.position.x, playerTransform.position.y + .35f), 1.3f, 1 << LayerMask.NameToLayer("Ground")); if (hitColliders.Length == 0) { return; } GameObject defaultGameObject = hitColliders[0].attachedRigidbody.gameObject; int i = 0; while (i < hitColliders.Length) { if (!GameObject.ReferenceEquals(defaultGameObject, hitColliders[i].attachedRigidbody.gameObject)) { return; } i++; } LevelPoint nextRoom = this; LevelPoint currentRoom = defaultGameObject.GetComponent <LevelPoint>(); Debug.Log("all of circle in one room"); if (!isLocked && nextRoom.levelIndex != currentRoom.levelIndex) { LevelManager.singleton.disableCollisionBetweenLevels(collision.otherCollider.name, currentRoom, nextRoom); } return; }
public override void Compute(LevelPoint origin, int rangeLimit) { _setVisible(origin.X, origin.Y); for (uint octant = 0; octant < 8; octant++) { Compute(octant, origin, rangeLimit, 1, new Slope(1, 1), new Slope(0, 1)); } }
public void Run(Vector2Int origin, int sightLimit) { var levelPoint = new LevelPoint { X = (uint)origin.x, Y = (uint)origin.y }; map.ClearVisibility(); fovComputer.Compute(levelPoint, sightLimit); }
public override bool Equals(object obj) { if (!GetType().Equals(obj.GetType())) { return(false); } LevelPoint p = (LevelPoint)obj; return(p.X == X && p.Y == Y); }
public void SaveUnlockedLevel(LevelPoint level) { for (int i = 0; i < levelConScript.levels.Length; i++) { if (levelConScript.levels[i].ToString() == level.ToString()) { PlayerPrefs.SetString("locked/unlocked" + levelConScript.levels[i].ToString(), "unlocked"); PlayerPrefs.Save(); } } }
public void MakeLevelNotInteractable(LevelPoint level) { if (level.GetComponent <Image> () != null) { level.GetComponent <Image> ().sprite = lockSprite; } else { level.GetComponent <SpriteRenderer>().sprite = lockSprite; } }
public void MakeLevelButtonInteractable(LevelPoint level) { if (level.GetComponent <Image> () != null) { level.GetComponent <Image> ().sprite = unlockSprite; } else { level.GetComponent <SpriteRenderer>().sprite = unlockSprite; } //level.GetComponent<Button>().interactable = true; }
private void generateEndPoint() { while (true) { EndPoint = FourCorners[Random.Range(0, 4)]; if (!EndPoint.Equals(StartPoint)) { break; } } }
/// <summary> /// Находит список тригеров соприкасающихся с точкой и возвращает их модели /// </summary> private IEnumerable <TriggerModel> GetTriggersModelFromPoint(LevelPoint point) { var triggers = new List <TriggerModel>(); foreach (var triggerModel in TriggerModels) { if (triggerModel.Platform.Coincidences(point)) { triggers.Add(triggerModel); } } return(triggers.Count > 0 ? triggers : null); }
private void OnTriggerStay2D(Collider2D collision) { if (collision.name == "Player") { if (Input.GetKeyDown(KeyCode.Space)) { LevelPoint level = gameObject.GetComponentInParent <LevelPoint>(); if (level != null) { GameManager.singelton.currentLevel = level.levelIndex; LevelManager.singleton.loadLevelScene(level.levelIndex); } } } }
public void disableCollisionBetweenLevels(string colliderName, LevelPoint currentRoom, LevelPoint otherRoom) { BoxCollider2D thisRoomCollider = null, otherRoomCollider = null; if (colliderName == "Bottom") { otherRoomCollider = otherRoom.bottom; thisRoomCollider = currentRoom.top; } else if (colliderName == "Top") { otherRoomCollider = otherRoom.top; thisRoomCollider = currentRoom.bottom; } else if (colliderName == "BottomRight") { otherRoomCollider = otherRoom.bottomRight; thisRoomCollider = currentRoom.topLeft; } else if (colliderName == "BottomLeft") { otherRoomCollider = otherRoom.bottomLeft; thisRoomCollider = currentRoom.topRight; } else if (colliderName == "TopLeft") { otherRoomCollider = otherRoom.topLeft; thisRoomCollider = currentRoom.bottomRight; } else if (colliderName == "TopRight") { otherRoomCollider = otherRoom.topRight; thisRoomCollider = currentRoom.bottomLeft; } else { Debug.Log("error Happen"); } thisRoomCollider.enabled = false; otherRoomCollider.enabled = false; Debug.Log("will disable " + otherRoomCollider.name + " and " + thisRoomCollider.name); return; }
private void generateObjectivePoint() { int count = 0; while (true) { if (count > FloorPoints.Count * FloorPoints.Count) { MinObjectiveDistance--; count = 0; } ObjectivePoint = FloorPoints[Random.Range(0, FloorPoints.Count)]; count++; if (Mathf.Abs(ObjectivePoint.X - StartPoint.X) + Mathf.Abs(ObjectivePoint.Y - StartPoint.Y) > MinObjectiveDistance) { break; } } }
public void getClosesetLevel(int currentLevelIndex, Transform collisionTransform) { float lowestDistance = 10000f; LevelPoint closesetLevel = null; for (var x = 0; x < levels.Count; x++) { if (currentLevelIndex == levels[x].levelIndex) { continue; } float currDistance = Vector2.Distance(levels[x].transform.position, collisionTransform.position); if (currDistance < lowestDistance) { lowestDistance = currDistance; closesetLevel = levels[x]; } } //Debug.Log(closesetLevel.levelIndex + " Distance: " + lowestDistance); }
private void changeDirection() { m_direction.X = 0; m_direction.Y = 0; int dir = Rand.Next(10000) % 4; switch (dir) { case 0: m_direction.X = -1; break; case 1: m_direction.Y = -1; break; case 2: m_direction.X = 1; break; case 3: m_direction.Y = 1; break; case 4: m_direction.X = 1; m_direction.Y = 1; break; } if (m_direction.X == m_lastDirection.Y && m_direction.Y == m_lastDirection.Y) { changeDirection(); } else { m_lastDirection = m_direction; } }
public LevelPoint[] RunPath() { if (m_points != null) { return(m_points); } changeDirection(); int len = Rand.Next(m_minSize, m_maxSize); LevelPoint[] path = new LevelPoint[len]; path[0].X = m_startX; path[0].Y = m_startY; for (int i = 1; i < len; i++) { try_again: //should change direction if (Rand.Next(1000) % 3 == 0) { changeDirection(); } path[i].X = path[i - 1].X + m_direction.X; path[i].Y = path[i - 1].Y + m_direction.Y; if (path[i].X >= m_levelWith || path[i].Y >= m_levelHeigth || path[i].X < 0 || path[i].Y < 0) { goto try_again; } } m_points = path; return(path); }
// NOTE: the code duplication between BlocksLight and SetVisible is for performance. don't refactor the octant // translation out unless you don't mind an 18% drop in speed bool BlocksLight(uint x, uint y, uint octant, LevelPoint origin) { uint nx = origin.X, ny = origin.Y; switch (octant) { case 0: nx += x; ny -= y; break; case 1: nx += y; ny -= x; break; case 2: nx -= y; ny -= x; break; case 3: nx -= x; ny -= y; break; case 4: nx -= x; ny += y; break; case 5: nx -= y; ny += x; break; case 6: nx += y; ny += x; break; case 7: nx += x; ny += y; break; } return(_blocksLight((int)nx, (int)ny)); }
void SetVisible(uint x, uint y, uint octant, LevelPoint origin) { uint nx = origin.X, ny = origin.Y; switch (octant) { case 0: nx += x; ny -= y; break; case 1: nx += y; ny -= x; break; case 2: nx -= y; ny -= x; break; case 3: nx -= x; ny -= y; break; case 4: nx -= x; ny += y; break; case 5: nx -= y; ny += x; break; case 6: nx += y; ny += x; break; case 7: nx += x; ny += y; break; } _setVisible((int)nx, (int)ny); }
private void offerAsCorner(LevelPoint point) { if ((FourCorners[UP_LEFT].X - FourCorners[UP_LEFT].Y) / 2f > (point.X - point.Y) / 2f) { FourCorners[UP_LEFT] = point; } if ((FourCorners[UP_RIGTH].X + FourCorners[UP_RIGTH].Y) / 2f < (point.X + point.Y) / 2f) { FourCorners[UP_RIGTH] = point; } if ((FourCorners[DOWN_LEFT].X + FourCorners[DOWN_LEFT].Y) / 2f > (point.X + point.Y) / 2f) { FourCorners[DOWN_LEFT] = point; } if ((FourCorners[DOWN_RIGTH].X - FourCorners[DOWN_RIGTH].Y) / 2f < (point.X - point.Y) / 2f) { FourCorners[DOWN_RIGTH] = point; } /*if(FourCorners[UP_LEFT].Y <= point.Y) * { * if(FourCorners[UP_LEFT].X >= point.X) * { * FourCorners[UP_LEFT] = point; * } * } * else if (FourCorners[UP_LEFT].X > point.X) * { * if((FourCorners[UP_LEFT].Y - point.Y) < (LevelHeigth / 8)) * { * FourCorners[UP_LEFT] = point; * } * } * * if (FourCorners[UP_RIGTH].Y <= point.Y) * { * if (FourCorners[UP_RIGTH].X <= point.X) * { * FourCorners[UP_RIGTH] = point; * } * } * else if (FourCorners[UP_RIGTH].X < point.X) * { * if ((FourCorners[UP_RIGTH].Y - point.Y) < (LevelHeigth / 8)) * { * FourCorners[UP_RIGTH] = point; * } * } * * if (FourCorners[DOWN_LEFT].Y >= point.Y) * { * if (FourCorners[DOWN_LEFT].X >= point.X) * { * FourCorners[DOWN_LEFT] = point; * } * } * else if (FourCorners[DOWN_LEFT].X > point.X) * { * if ((FourCorners[DOWN_LEFT].Y - point.Y) < (LevelHeigth / 8)) * { * FourCorners[DOWN_LEFT] = point; * } * } * * if (FourCorners[DOWN_RIGTH].Y >= point.Y) * { * if (FourCorners[DOWN_RIGTH].X <= point.X) * { * FourCorners[DOWN_RIGTH] = point; * } * } * else if (FourCorners[DOWN_RIGTH].X < point.X) * { * if ((FourCorners[DOWN_RIGTH].Y - point.Y) < (LevelHeigth / 8)) * { * FourCorners[DOWN_RIGTH] = point; * } * }*/ }
public void Generate() { char[,] worldData = new char[LevelWidth, LevelHeigth]; fill(worldData, '#'); purgeOld(); GeneratePaths(worldData); m_world.InitMap(new Map(worldData)); m_world.Draw(); if (LevelType != LevelType.Ambush) { StartPoint = FourCorners[Random.Range(0, 4)]; generateObjectivePoint(); generateEndPoint(); } EnemySpawnPoints = new LevelPoint[Random.Range(MinEnemySpawnCount, MaxEnemySpawnCount)]; generateSpawnEnemySpawnPoints(); if (Verbose) { foreach (World.Tile tile in m_world.GetGrid().GetCell(FourCorners[UP_LEFT].X, FourCorners[UP_LEFT].Y).GetTiles()) { tile.GetGO().GetComponent <MeshRenderer>().material.color = Color.red; } foreach (World.Tile tile in m_world.GetGrid().GetCell(FourCorners[UP_RIGTH].X, FourCorners[UP_RIGTH].Y).GetTiles()) { tile.GetGO().GetComponent <MeshRenderer>().material.color = Color.magenta; } foreach (World.Tile tile in m_world.GetGrid().GetCell(FourCorners[DOWN_LEFT].X, FourCorners[DOWN_LEFT].Y).GetTiles()) { tile.GetGO().GetComponent <MeshRenderer>().material.color = Color.cyan; } foreach (World.Tile tile in m_world.GetGrid().GetCell(FourCorners[DOWN_RIGTH].X, FourCorners[DOWN_RIGTH].Y).GetTiles()) { tile.GetGO().GetComponent <MeshRenderer>().material.color = Color.green; } foreach (World.Tile tile in m_world.GetGrid().GetCell(StartPoint.X, StartPoint.Y).GetTiles()) { tile.GetGO().GetComponent <MeshRenderer>().material.color = Color.yellow; } foreach (LevelPoint point in EnemySpawnPoints) { foreach (World.Tile tile in m_world.GetGrid().GetCell(point.X, point.Y).GetTiles()) { tile.GetGO().GetComponent <MeshRenderer>().material.color = Color.black; } } } if (LevelType != LevelType.Ambush) { foreach (World.Tile tile in m_world.GetGrid().GetCell(ObjectivePoint.X, ObjectivePoint.Y).GetTiles()) { tile.GetGO().GetComponent <MeshRenderer>().material.color = new Color(0.2f, 0.4f, 0.1f); } } if (LevelType != LevelType.Ambush) { foreach (World.Tile tile in m_world.GetGrid().GetCell(EndPoint.X, EndPoint.Y).GetTiles()) { tile.GetGO().GetComponent <MeshRenderer>().material.color = Color.red; } } }
/// <summary> /// Есть ли совпадения с точками платформы /// </summary> /// <param name="point">Точка, с которой ищем совпадения</param> public bool Coincidences(LevelPoint point) { return(point == FirstPoint || point == SecondPoint); }
void Compute(uint octant, LevelPoint origin, int rangeLimit, uint x, Slope top, Slope bottom) { // throughout this function there are references to various parts of tiles. a tile's coordinates refer to its // center, and the following diagram shows the parts of the tile and the vectors from the origin that pass through // those parts. given a part of a tile with vector u, a vector v passes above it if v > u and below it if v < u // g center: y / x // a------b a top left: (y*2+1) / (x*2-1) i inner top left: (y*4+1) / (x*4-1) // | /\ | b top right: (y*2+1) / (x*2+1) j inner top right: (y*4+1) / (x*4+1) // |i/__\j| c bottom left: (y*2-1) / (x*2-1) k inner bottom left: (y*4-1) / (x*4-1) //e|/| |\|f d bottom right: (y*2-1) / (x*2+1) m inner bottom right: (y*4-1) / (x*4+1) // |\|__|/| e middle left: (y*2) / (x*2-1) // |k\ /m| f middle right: (y*2) / (x*2+1) a-d are the corners of the tile // | \/ | g top center: (y*2+1) / (x*2) e-h are the corners of the inner (wall) diamond // c------d h bottom center: (y*2-1) / (x*2) i-m are the corners of the inner square (1/2 tile width) // h for (; x <= (uint)rangeLimit; x++) // (x <= (uint)rangeLimit) == (rangeLimit < 0 || x <= rangeLimit) { // compute the Y coordinates of the top and bottom of the sector. we maintain that top > bottom uint topY; if (top.X == 1) // if top == ?/1 then it must be 1/1 because 0/1 < top <= 1/1. this is special-cased because top { // starts at 1/1 and remains 1/1 as long as it doesn't hit anything, so it's a common case topY = x; } else // top < 1 { // get the tile that the top vector enters from the left. since our coordinates refer to the center of the // tile, this is (x-0.5)*top+0.5, which can be computed as (x-0.5)*top+0.5 = (2(x+0.5)*top+1)/2 = // ((2x+1)*top+1)/2. since top == a/b, this is ((2x+1)*a+b)/2b. if it enters a tile at one of the left // corners, it will round up, so it'll enter from the bottom-left and never the top-left topY = ((x * 2 - 1) * top.Y + top.X) / (top.X * 2); // the Y coordinate of the tile entered from the left // now it's possible that the vector passes from the left side of the tile up into the tile above before // exiting from the right side of this column. so we may need to increment topY if (BlocksLight(x, topY, octant, origin)) // if the tile blocks light (i.e. is a wall)... { // if the tile entered from the left blocks light, whether it passes into the tile above depends on the shape // of the wall tile as well as the angle of the vector. if the tile has does not have a beveled top-left // corner, then it is blocked. the corner is beveled if the tiles above and to the left are not walls. we can // ignore the tile to the left because if it was a wall tile, the top vector must have entered this tile from // the bottom-left corner, in which case it can't possibly enter the tile above. // // otherwise, with a beveled top-left corner, the slope of the vector must be greater than or equal to the // slope of the vector to the top center of the tile (x*2, topY*2+1) in order for it to miss the wall and // pass into the tile above if (top.GreaterOrEqual(topY * 2 + 1, x * 2) && !BlocksLight(x, topY + 1, octant, origin)) { topY++; } } else // the tile doesn't block light { // since this tile doesn't block light, there's nothing to stop it from passing into the tile above, and it // does so if the vector is greater than the vector for the bottom-right corner of the tile above. however, // there is one additional consideration. later code in this method assumes that if a tile blocks light then // it must be visible, so if the tile above blocks light we have to make sure the light actually impacts the // wall shape. now there are three cases: 1) the tile above is clear, in which case the vector must be above // the bottom-right corner of the tile above, 2) the tile above blocks light and does not have a beveled // bottom-right corner, in which case the vector must be above the bottom-right corner, and 3) the tile above // blocks light and does have a beveled bottom-right corner, in which case the vector must be above the // bottom center of the tile above (i.e. the corner of the beveled edge). // // now it's possible to merge 1 and 2 into a single check, and we get the following: if the tile above and to // the right is a wall, then the vector must be above the bottom-right corner. otherwise, the vector must be // above the bottom center. this works because if the tile above and to the right is a wall, then there are // two cases: 1) the tile above is also a wall, in which case we must check against the bottom-right corner, // or 2) the tile above is not a wall, in which case the vector passes into it if it's above the bottom-right // corner. so either way we use the bottom-right corner in that case. now, if the tile above and to the right // is not a wall, then we again have two cases: 1) the tile above is a wall with a beveled edge, in which // case we must check against the bottom center, or 2) the tile above is not a wall, in which case it will // only be visible if light passes through the inner square, and the inner square is guaranteed to be no // larger than a wall diamond, so if it wouldn't pass through a wall diamond then it can't be visible, so // there's no point in incrementing topY even if light passes through the corner of the tile above. so we // might as well use the bottom center for both cases. uint ax = x * 2; // center if (BlocksLight(x + 1, topY + 1, octant, origin)) { ax++; // use bottom-right if the tile above and right is a wall } if (top.Greater(topY * 2 + 1, ax)) { topY++; } } } uint bottomY; if (bottom.Y == 0) // if bottom == 0/?, then it's hitting the tile at Y=0 dead center. this is special-cased because { // bottom.Y starts at zero and remains zero as long as it doesn't hit anything, so it's common bottomY = 0; } else // bottom > 0 { bottomY = ((x * 2 - 1) * bottom.Y + bottom.X) / (bottom.X * 2); // the tile that the bottom vector enters from the left // code below assumes that if a tile is a wall then it's visible, so if the tile contains a wall we have to // ensure that the bottom vector actually hits the wall shape. it misses the wall shape if the top-left corner // is beveled and bottom >= (bottomY*2+1)/(x*2). finally, the top-left corner is beveled if the tiles to the // left and above are clear. we can assume the tile to the left is clear because otherwise the bottom vector // would be greater, so we only have to check above if (bottom.GreaterOrEqual(bottomY * 2 + 1, x * 2) && BlocksLight(x, bottomY, octant, origin) && !BlocksLight(x, bottomY + 1, octant, origin)) { bottomY++; } } // go through the tiles in the column now that we know which ones could possibly be visible int wasOpaque = -1; // 0:false, 1:true, -1:not applicable for (uint y = topY; (int)y >= (int)bottomY; y--) // use a signed comparison because y can wrap around when decremented { if (rangeLimit < 0 || GetDistance((int)x, (int)y) <= rangeLimit) // skip the tile if it's out of visual range { bool isOpaque = BlocksLight(x, y, octant, origin); // every tile where topY > y > bottomY is guaranteed to be visible. also, the code that initializes topY and // bottomY guarantees that if the tile is opaque then it's visible. so we only have to do extra work for the // case where the tile is clear and y == topY or y == bottomY. if y == topY then we have to make sure that // the top vector is above the bottom-right corner of the inner square. if y == bottomY then we have to make // sure that the bottom vector is below the top-left corner of the inner square bool isVisible = isOpaque || ((y != topY || top.Greater(y * 4 - 1, x * 4 + 1)) && (y != bottomY || bottom.Less(y * 4 + 1, x * 4 - 1))); // NOTE: if you want the algorithm to be either fully or mostly symmetrical, replace the line above with the // following line (and uncomment the Slope.LessOrEqual method). the line ensures that a clear tile is visible // only if there's an unobstructed line to its center. if you want it to be fully symmetrical, also remove // the "isOpaque ||" part and see NOTE comments further down // bool isVisible = isOpaque || ((y != topY || top.GreaterOrEqual(y, x)) && (y != bottomY || bottom.LessOrEqual(y, x))); if (isVisible) { SetVisible(x, y, octant, origin); } // if we found a transition from clear to opaque or vice versa, adjust the top and bottom vectors if (x != rangeLimit) // but don't bother adjusting them if this is the last column anyway { if (isOpaque) { if (wasOpaque == 0) // if we found a transition from clear to opaque, this sector is done in this column, { // so adjust the bottom vector upward and continue processing it in the next column // if the opaque tile has a beveled top-left corner, move the bottom vector up to the top center. // otherwise, move it up to the top left. the corner is beveled if the tiles above and to the left are // clear. we can assume the tile to the left is clear because otherwise the vector would be higher, so // we only have to check the tile above uint nx = x * 2, ny = y * 2 + 1; // top center by default // NOTE: if you're using full symmetry and want more expansive walls (recommended), comment out the next line if (BlocksLight(x, y + 1, octant, origin)) { nx--; // top left if the corner is not beveled } if (top.Greater(ny, nx)) // we have to maintain the invariant that top > bottom, so the new sector { // created by adjusting the bottom is only valid if that's the case // if we're at the bottom of the column, then just adjust the current sector rather than recursing // since there's no chance that this sector can be split in two by a later transition back to clear if (y == bottomY) { bottom = new Slope(ny, nx); break; } // don't recurse unless necessary else { Compute(octant, origin, rangeLimit, x + 1, top, new Slope(ny, nx)); } } else // the new bottom is greater than or equal to the top, so the new sector is empty and we'll ignore { // it. if we're at the bottom of the column, we'd normally adjust the current sector rather than if (y == bottomY) { return; // recursing, so that invalidates the current sector and we're done } } } wasOpaque = 1; } else { if (wasOpaque > 0) // if we found a transition from opaque to clear, adjust the top vector downwards { // if the opaque tile has a beveled bottom-right corner, move the top vector down to the bottom center. // otherwise, move it down to the bottom right. the corner is beveled if the tiles below and to the right // are clear. we know the tile below is clear because that's the current tile, so just check to the right uint nx = x * 2, ny = y * 2 + 1; // the bottom of the opaque tile (oy*2-1) equals the top of this tile (y*2+1) // NOTE: if you're using full symmetry and want more expansive walls (recommended), comment out the next line if (BlocksLight(x + 1, y + 1, octant, origin)) { nx++; // check the right of the opaque tile (y+1), not this one } // we have to maintain the invariant that top > bottom. if not, the sector is empty and we're done if (bottom.GreaterOrEqual(ny, nx)) { return; } top = new Slope(ny, nx); } wasOpaque = 0; } } } } // if the column didn't end in a clear tile, then there's no reason to continue processing the current sector // because that means either 1) wasOpaque == -1, implying that the sector is empty or at its range limit, or 2) // wasOpaque == 1, implying that we found a transition from clear to opaque and we recursed and we never found // a transition back to clear, so there's nothing else for us to do that the recursive method hasn't already. (if // we didn't recurse (because y == bottomY), it would have executed a break, leaving wasOpaque equal to 0.) if (wasOpaque != 0) { break; } } }