Hi,
I have been working on a little prototype for an infinite runner game and I seem to have come across a weird issue. I have tried to re-implement it twice now but the same issue keeps popping up.
The basic idea around the generation is to build the level out of little components, each with varying sizes which stick together, they move left, the player is placed at a fixed point and the moving level gives the illusion the player is moving (in combination with animations etc). Once a component of the level reaches the end of the screen (left) it will move back to the right and stick on the end again.
The issue I am having is that when I am re-positioning the tiles to the end, I figured that just taking the width of the last tile, and the width of the new tile, halfing each and combining them will give the difference and use that to offset it relative to the last tile. This method works, however for some reason, the first tile generated, when it tried to be re-positioned at the end it gains this "phantom offset" of 0.166 every time, debugging values it prints everything correctly, but I dont understand why for one specific tile all the time it has this offset, yet every tile is identical and generated programatically .
To image below may explain it easier :
Whenever the Red tile (first tile generated) is reset to the back where the Blue tile (last tile generated) it will, instead of lining up correctly, it will have a 0.166 unit gap between the tiles, however if the next grey tile is reset, it will not have this issue.
![alt text][1]
EDIT:
The three main classes in play with this are shown below, TileController is the main class which creates and manages tiles resetting, Tiles themselves are on each tile in the world and TileType is an object passed around which controls the behaviour of the tiles (allowing custom types of tiles etc)
Tile Controller:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class TileController : MonoBehaviour
{
///
/// How many tiles are to be used as part of the level
///
[SerializeField]
private int _tileCount = 64;
///
/// All the tiles which are currently created
///
private Tile[] _tiles;
///
/// A queue of available tiles to be used
///
private Queue _tileQueue;
///
/// The last tile to be spawned
///
[SerializeField]
private Tile _lastTile;
///
/// Default tile height to spawn tiles at
///
[SerializeField]
private float _defaultTileHeight = 1.0f;
///
/// The generator instance which manages which tiles are to spawn
///
private TileGenerator _tileGenerator;
///
/// Prefab of the template tile to be used
///
[SerializeField]
private Tile _templateTileInstance;
///
/// The speed of the tiles in the level
///
[SerializeField]
private float _tileSpeed = 3.2f;
///
/// The X position at which the tiles are considered "completed"
///
[SerializeField]
private float _despawnX = -5.0f;
public float defaultTileHeight
{
get { return _defaultTileHeight; }
}
public float despawnX
{
get { return _despawnX; }
}
///
/// When a tile has completed its cycle and reached the endpoint
///
///
public void tileFinished(Tile tile)
{
TileType next = _tileGenerator.getNextTile();
Vector2 position = getSpawnPosition(next);
_lastTile = tile;
tile.spawnTile(next, position, _tileSpeed);
}
///
/// Get the position to spawn the next tile at
///
///
private Vector2 getSpawnPosition(TileType tileType)
{
Vector2 p = _lastTile.transform.position;
return p + new Vector2((_lastTile.tileWidth / 2.0f) + (tileType.width / 2.0f) , 0);
}
///
/// Get an available tile
///
///
///
private Tile getTile(bool firstTile)
{
Tile tile;
if (_tileQueue.Count == 0)
{
tile = Instantiate(_templateTileInstance);
tile.Initialise(this, firstTile);
}
else
{
tile = _tileQueue.Dequeue();
}
return tile;
}
///
/// Create all the initial tiles
///
private void spawnInitialTiles()
{
Tile tile;
TileType tileType;
Vector2 pos;
_tiles = new Tile[_tileCount];
// Create first tile (because there will be no "previous tile position" on start
tile = getTile(true);
tileType = _tileGenerator.getNextTile();
pos = new Vector2(0, 0);
_lastTile = tile;
_tiles[0] = tile;
tile.spawnTile(tileType, pos, _tileSpeed);
// Loop all but first tile
for (int i = 1; i < _tileCount; i++)
{
tile = getTile(false);
tileType = _tileGenerator.getNextTile();
pos = getSpawnPosition(tileType);
_lastTile = tile;
_tiles[i] = tile;
tile.spawnTile(tileType, pos, _tileSpeed);
}
}
void Start()
{
_tileQueue = new Queue();
_tileGenerator = new TileGenerator(this);
spawnInitialTiles();
}
}
Tile:
using UnityEngine;
using System.Collections;
public class Tile : MonoBehaviour
{
///
/// The speed this tile is moving
///
private float _speed;
///
/// Whether or not the tile is currently in play
///
private bool _inPlay;
///
/// Whether or not this tile is the first (this is a hacky fix for the precision bug)
///
private bool _firstTile;
///
/// The type of the tile (used to determine the behaviour of the tile-set
///
[SerializeField]
private TileType _tileType;
///
/// A reference to the tile controller which holds this tile
///
private TileController _tileController;
///
/// Whether or not the tile has been initialised
///
private bool _initialised;
///
/// The default tile which makes up the floor of the tile by default
///
[SerializeField]
private GameObject _defaultTile;
///
/// The width of the tiletype currently being used
///
public float tileWidth
{
get
{
return _tileType.width;
}
}
///
/// The height of the tiletype currently being used
///
public float tileHeight
{
get
{
return _tileType.height;
}
}
///
/// Initialise the tile the first time it is created, allows referencing to the controller
///
///
public void Initialise(TileController tileController, bool firstTile)
{
if (!_initialised)
{
_tileController = tileController;
_inPlay = false;
_firstTile = firstTile;
}
}
///
/// Toggle the "default white tile" which makes up the sector
///
///
public void toggleDefaultTile(bool state)
{
_defaultTile.SetActive(state);
}
///
/// Spawn the tile in the world, set its position, initialise values and pass the spawn onto the tiletype
///
///
///
///
public void spawnTile(TileType tileType, Vector2 position, float speed)
{
_tileType = tileType;
_speed = speed;
_inPlay = true;
transform.position = position;
transform.localScale = new Vector3(_tileType.width, 1, 1);
tileType.spawn(this, _tileController);
}
///
/// Called every logical cycle
///
public void Update2()
{
if (_inPlay)
{
// Is the tile outside the play zone?
if (transform.position.x <= _tileController.despawnX)
{
_inPlay = false;
_tileType.cleanup(this, _tileController);
_tileController.tileFinished(this);
}
else // In play, dont reset, move and update
{
transform.Translate(new Vector3((-_speed * Time.deltaTime), 0, 0));
_tileType.update(this, _tileController);
}
}
}
///
/// Called on startup
///
public void Start()
{
}
}
TileType :
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[System.Serializable]
public class TileType
{
[SerializeField]
private float _width;
[SerializeField]
private float _height;
// A store of all square primitive world tiles to be used
protected static Queue _primitiveStore = new Queue();
public float width
{
get { return _width; }
}
public float height
{
get { return _height; }
}
public TileType(float width, float height)
{
_width = width;
_height = height;
}
///
/// Update the tile every timestep, tile logic update loop
///
///
///
public virtual void update(Tile tile, TileController tileController)
{
}
///
/// When the tile has been spawned, used to initialise and setup the tile before it comes into play
///
///
///
public virtual void spawn(Tile tile, TileController tileController)
{
//_width = 1.0f;
//_height = tileController.defaultTileHeight;
}
///
/// When the tile has reached the despawn point, used to cleanup and reset the tiles behaviour
///
///
///
public virtual void cleanup(Tile tile, TileController tileController)
{
tile.toggleDefaultTile(true);
}
///
/// Pass an extra data to the new instance
///
///
public virtual void cloneExtraData(TileType clone)
{
}
///
/// Create a clone of this object
///
public virtual TileType clone()
{
TileType instance = new TileType(_width,_height);
cloneExtraData(instance);
return instance;
}
///
/// Get a primitive square from the static store
///
///
protected static GameObject getPrimitive()
{
if (_primitiveStore.Count == 0)
{
return creatPrimitive();
}
return _primitiveStore.Dequeue();
}
///
/// Create a new primitive square, usually used when there are no more in the store
///
///
protected static GameObject creatPrimitive()
{
GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Quad);
return obj;
}
///
/// Store a primitive square back in the store
///
///
protected static void storePrimitive(GameObject primitive)
{
primitive.transform.position = new Vector3(0, -50, 0);
_primitiveStore.Enqueue(primitive);
}
public override string ToString()
{
return string.Format("(w:{0},h:{1})", _width, _height);
}
}
[1]: /storage/temp/62711-img01.png
↧