public TilemapTileDrawSource(Tilemap tilemap, Point2 origin, Grid<bool> area) { if (tilemap == null) throw new ArgumentNullException("tilemap"); this.tilemap = tilemap; this.origin = origin; this.area = new Grid<bool>(area); }
public override bool Convert(ConvertOperation convert) { // If we already have a renderer in the result set, consider generating // a tilemap to be not the right course of action. if (convert.Result.OfType<ICmpRenderer>().Any()) return false; List<object> results = new List<object>(); List<Tileset> availData = convert.Perform<Tileset>().ToList(); // Generate objects foreach (Tileset tileset in availData) { if (convert.IsObjectHandled(tileset)) continue; // Retrieve previously generated GameObjects and Tilemaps for re-use GameObject gameobj = convert.Result.OfType<GameObject>().FirstOrDefault(); Tilemap tilemap = convert.Result.OfType<Tilemap>().FirstOrDefault(); TilemapRenderer tilemapRenderer = convert.Result.OfType<TilemapRenderer>().FirstOrDefault(); if (tilemap == null && gameobj != null) tilemap = gameobj.GetComponent<Tilemap>(); // Create a new Tilemap (and TilemapRenderer) if none did exist before if (tilemap == null) { tilemap = new Tilemap(); TilemapsSetupUtility.SetupTilemap(tilemap, tileset); // Add a renderer for this Tilemap to the result list, if there was none before if (tilemapRenderer == null) { results.Add(new TilemapRenderer()); } } // Configure the Tilemap according to the Tileset we're converting tilemap.Tileset = tileset; // Add the Tilemap to our result set results.Add(tilemap); convert.SuggestResultName(tilemap, tileset.Name); convert.MarkObjectHandled(tileset); } convert.AddResult(results); return false; }
/// <summary> /// Prepares the specified <see cref="Tilemap"/> for user editing using the specified size. /// </summary> /// <param name="tilemap"></param> /// <param name="tilesetRef"></param> /// <param name="width"></param> /// <param name="height"></param> /// <param name="isUpperLayer"></param> public static void SetupTilemap(Tilemap tilemap, ContentRef<Tileset> tilesetRef, int width, int height, bool isUpperLayer) { Tileset tileset = tilesetRef.Res; // Determine the first tile index that matches the layer type. int fillTileIndex = GetDefaultTileIndex(tileset, isUpperLayer); // Resize the Tilemap and fill it with the first visually non-empty tile. tilemap.Tileset = tileset; tilemap.Resize(width, height); tilemap.BeginUpdateTiles().Fill( new Tile(fillTileIndex), 0, 0, tilemap.Size.X, tilemap.Size.Y); tilemap.EndUpdateTiles(); }
/// <summary> /// Prepares the specified <see cref="Tilemap"/> for user editing using the default size. /// </summary> /// <param name="tilemap"></param> /// <param name="tilesetRef"></param> public static void SetupTilemap(Tilemap tilemap, ContentRef<Tileset> tilesetRef) { SetupTilemap(tilemap, tilesetRef, DefaultTilemapSize.X, DefaultTilemapSize.Y, false); }
private void DualityEditorApp_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (e.SameObjects) return; if (!e.AffectedCategories.HasFlag(ObjectSelection.Category.GameObjCmp)) return; // Tilemap selection changed Tilemap newSelection = TilemapsEditorSelectionParser.QuerySelectedTilemap(); if (this.selectedTilemap != newSelection) { this.selectedTilemap = newSelection; if (this.Mouseover) this.OnMouseMove(); this.Invalidate(); this.UpdateActionToolButtons(); } }
protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); this.hoveredTile = InvalidTile; this.hoveredRenderer = null; if (this.actionTool == this.toolNone) { this.activeTool = this.toolNone; this.activeTilemap = null; this.activeAreaOrigin = InvalidTile; this.activeArea.ResizeClear(0, 0); } this.UpdateCursor(); this.Invalidate(); }
protected override void OnEnterState() { base.OnEnterState(); // Initialize available tools, actions and the toolbar containing them this.InitAvailableActions(); this.InitAvailableTools(); this.InitToolButtons(); // Register events DualityEditorApp.SelectionChanged += this.DualityEditorApp_SelectionChanged; DualityEditorApp.ObjectPropertyChanged += this.DualityEditorApp_ObjectPropertyChanged; DualityEditorApp.UpdatingEngine += this.DualityEditorApp_UpdatingEngine; Scene.Entered += this.Scene_Entered; // Initial update this.UpdateTilemapToolButtons(); this.UpdateActionToolButtons(); this.selectedTilemap = TilemapsEditorSelectionParser.QuerySelectedTilemap(); // If we're already focused when entering the state, publish the currently selected tilemap tool if (this.Focused) this.SelectToolInInspector(); }
void ITilemapToolEnvironment.PerformEditTiles(EditTilemapActionType actionType, Tilemap tilemap, Point2 pos, Grid<bool> brush, ITileDrawSource source, Point2 sourceOffset) { Grid<Tile> drawPatch = new Grid<Tile>(brush.Width, brush.Height); source.FillTarget(drawPatch, sourceOffset); UndoRedoManager.Do(new EditTilemapAction( tilemap, actionType, pos, drawPatch, brush, this.actionTool.Settings.UseAutoTiling)); }
private void UpdateActiveState() { TilemapTool lastActiveTool = this.activeTool; Tilemap lastActiveTilemap = this.activeTilemap; ICmpTilemapRenderer lastActiveRenderer = this.activeRenderer; // If an action is currently being performed, that action will always be the active tool if (this.actionTool != this.toolNone) { this.activeTool = this.actionTool; } // Otherwise, determine what action the cursor would do right now else { // Determine the active tool dynamically based on user input state if (this.hoveredRenderer == null) this.activeTool = this.toolNone; else if (this.selectedTilemap == null) this.activeTool = this.toolSelect; else if (this.hoveredRenderer.ActiveTilemap != this.selectedTilemap) this.activeTool = this.toolSelect; else this.activeTool = this.overrideTool ?? this.selectedTool; // Keep in mind on what renderer and tilemap belong to the currently active tool this.activeTilemap = (this.hoveredRenderer != null) ? this.hoveredRenderer.ActiveTilemap : null; this.activeRenderer = this.hoveredRenderer; } // Determine the area that is affected by the current action this.activeTool.UpdatePreview(); // If our highlighted action changed, redraw view and update the cursor if (lastActiveTool != this.activeTool || lastActiveTilemap != this.activeTilemap || lastActiveRenderer != this.activeRenderer) { this.UpdateCursor(); this.Invalidate(); } }
private void RetrieveSourceTilemaps() { Tilemap localTilemap = this.GameObj.GetComponent<Tilemap>(); this.referenceTilemap = null; this.sourceTilemaps = new Tilemap[this.source.Length]; for (int i = 0; i < this.sourceTilemaps.Length; i++) { this.sourceTilemaps[i] = this.source[i].SourceTilemap ?? localTilemap; if (this.referenceTilemap == null && this.sourceTilemaps[i] != null) this.referenceTilemap = this.sourceTilemaps[i]; } }
private static Point2 GetTileCount(Tilemap[] tilemaps) { Point2 count = new Point2(int.MaxValue, int.MaxValue); for (int i = 0; i < tilemaps.Length; i++) { if (tilemaps[i] == null) continue; count.X = Math.Min(count.X, tilemaps[i].Size.X); count.Y = Math.Min(count.Y, tilemaps[i].Size.Y); } return count; }
private static TileInfo[][] GetRawTileData(Tilemap[] tilemaps) { TileInfo[][] tileData = new TileInfo[tilemaps.Length][]; for (int i = 0; i < tilemaps.Length; i++) { if (tilemaps[i] == null) continue; if (tilemaps[i].Tileset.Res == null) continue; tileData[i] = tilemaps[i].Tileset.Res.TileData.Data; } return tileData; }
public override void Draw(IDrawDevice device) { // Determine basic working data Tilemap tilemap = this.ActiveTilemap; Tileset tileset = tilemap != null ? tilemap.Tileset.Res : null; Point2 tileCount = tilemap != null ? tilemap.Size : new Point2(1, 1); Vector2 tileSize = tileset != null ? tileset.TileSize : Tileset.DefaultTileSize; // Early-out, if insufficient if (tilemap == null) { return; } if (tileset == null) { return; } // Determine the total size and origin of the rendered Tilemap Vector2 renderTotalSize = tileCount * tileSize; Vector2 renderOrigin = Vector2.Zero; this.origin.ApplyTo(ref renderOrigin, ref renderTotalSize); MathF.TransformCoord(ref renderOrigin.X, ref renderOrigin.Y, this.GameObj.Transform.Angle, this.GameObj.Transform.Scale); // Determine Tile visibility TilemapCulling.TileInput cullingIn = new TilemapCulling.TileInput { // Remember: All these transform values are in world space TilemapPos = this.GameObj.Transform.Pos + new Vector3(renderOrigin), TilemapScale = this.GameObj.Transform.Scale, TilemapAngle = this.GameObj.Transform.Angle, TileCount = tileCount, TileSize = tileSize }; TilemapCulling.TileOutput cullingOut = TilemapCulling.GetVisibleTileRect(device, cullingIn); int renderedTileCount = cullingOut.VisibleTileCount.X * cullingOut.VisibleTileCount.Y; // Determine rendering parameters Material material = (tileset != null ? tileset.RenderMaterial : null) ?? Material.Checkerboard.Res; ColorRgba mainColor = this.colorTint; // Determine and adjust data for Z offset generation float depthPerTile = -cullingIn.TileSize.Y * cullingIn.TilemapScale * this.tileDepthScale; if (this.tileDepthMode == TileDepthOffsetMode.Flat) { depthPerTile = 0.0f; } float originDepthOffset = Rect.Align(this.origin, 0, 0, 0, tileCount.Y * depthPerTile).Y; if (this.tileDepthMode == TileDepthOffsetMode.World) { originDepthOffset += (this.GameObj.Transform.Pos.Y / (float)tileSize.Y) * depthPerTile; } float renderBaseOffset = this.offset + this.tileDepthOffset * depthPerTile + originDepthOffset; // Prepare vertex generation data Vector2 tileXStep = cullingOut.XAxisWorld * cullingIn.TileSize.X; Vector2 tileYStep = cullingOut.YAxisWorld * cullingIn.TileSize.Y; Vector3 renderPos = cullingOut.RenderOriginWorld; float renderOffset = renderBaseOffset; Point2 tileGridPos = cullingOut.VisibleTileStart; // Reserve the required space for vertex data in our locally cached buffer const int MaxVerticesPerBatch = 65532; if (this.vertices == null) { this.vertices = new RawList <VertexC1P3T2>(); } this.vertices.Count = Math.Min(renderedTileCount * 4, MaxVerticesPerBatch); VertexC1P3T2[] vertexData = this.vertices.Data; // Prepare vertex data array for batch-submitting IReadOnlyGrid <Tile> tiles = tilemap.Tiles; TileInfo[] tileData = tileset.TileData.Data; int submittedTileCount = 0; int submittedBatchCount = 0; int vertexBaseIndex = 0; for (int tileIndex = 0; tileIndex < renderedTileCount; tileIndex++) { Tile tile = tiles[tileGridPos.X, tileGridPos.Y]; if (tile.Index < tileData.Length) { Rect uvRect = tileData[tile.Index].TexCoord0; bool visualEmpty = tileData[tile.Index].IsVisuallyEmpty; int tileBaseOffset = tileData[tile.Index].DepthOffset; float localDepthOffset = (tile.DepthOffset + tileBaseOffset) * depthPerTile; if (!visualEmpty) { vertexData[vertexBaseIndex + 0].Pos.X = renderPos.X; vertexData[vertexBaseIndex + 0].Pos.Y = renderPos.Y; vertexData[vertexBaseIndex + 0].Pos.Z = renderPos.Z; vertexData[vertexBaseIndex + 0].DepthOffset = renderOffset + localDepthOffset; vertexData[vertexBaseIndex + 0].TexCoord.X = uvRect.X; vertexData[vertexBaseIndex + 0].TexCoord.Y = uvRect.Y; vertexData[vertexBaseIndex + 0].Color = mainColor; vertexData[vertexBaseIndex + 1].Pos.X = renderPos.X + tileYStep.X; vertexData[vertexBaseIndex + 1].Pos.Y = renderPos.Y + tileYStep.Y; vertexData[vertexBaseIndex + 1].Pos.Z = renderPos.Z; vertexData[vertexBaseIndex + 1].DepthOffset = renderOffset + localDepthOffset + depthPerTile; vertexData[vertexBaseIndex + 1].TexCoord.X = uvRect.X; vertexData[vertexBaseIndex + 1].TexCoord.Y = uvRect.Y + uvRect.H; vertexData[vertexBaseIndex + 1].Color = mainColor; vertexData[vertexBaseIndex + 2].Pos.X = renderPos.X + tileXStep.X + tileYStep.X; vertexData[vertexBaseIndex + 2].Pos.Y = renderPos.Y + tileXStep.Y + tileYStep.Y; vertexData[vertexBaseIndex + 2].Pos.Z = renderPos.Z; vertexData[vertexBaseIndex + 2].DepthOffset = renderOffset + localDepthOffset + depthPerTile; vertexData[vertexBaseIndex + 2].TexCoord.X = uvRect.X + uvRect.W; vertexData[vertexBaseIndex + 2].TexCoord.Y = uvRect.Y + uvRect.H; vertexData[vertexBaseIndex + 2].Color = mainColor; vertexData[vertexBaseIndex + 3].Pos.X = renderPos.X + tileXStep.X; vertexData[vertexBaseIndex + 3].Pos.Y = renderPos.Y + tileXStep.Y; vertexData[vertexBaseIndex + 3].Pos.Z = renderPos.Z; vertexData[vertexBaseIndex + 3].DepthOffset = renderOffset + localDepthOffset; vertexData[vertexBaseIndex + 3].TexCoord.X = uvRect.X + uvRect.W; vertexData[vertexBaseIndex + 3].TexCoord.Y = uvRect.Y; vertexData[vertexBaseIndex + 3].Color = mainColor; bool vertical = tileData[tile.Index].IsVertical; if (vertical) { vertexData[vertexBaseIndex + 0].DepthOffset += depthPerTile; vertexData[vertexBaseIndex + 3].DepthOffset += depthPerTile; } submittedTileCount++; vertexBaseIndex += 4; } } tileGridPos.X++; renderPos.X += tileXStep.X; renderPos.Y += tileXStep.Y; if ((tileGridPos.X - cullingOut.VisibleTileStart.X) >= cullingOut.VisibleTileCount.X) { tileGridPos.X = cullingOut.VisibleTileStart.X; tileGridPos.Y++; renderPos = cullingOut.RenderOriginWorld; renderPos.X += tileYStep.X * (tileGridPos.Y - cullingOut.VisibleTileStart.Y); renderPos.Y += tileYStep.Y * (tileGridPos.Y - cullingOut.VisibleTileStart.Y); renderOffset = renderBaseOffset + tileGridPos.Y * depthPerTile; } // If we reached the maximum number of vertices per batch, submit early and restart if (vertexBaseIndex >= MaxVerticesPerBatch) { device.AddVertices( material, VertexMode.Quads, vertexData, vertexBaseIndex); vertexBaseIndex = 0; submittedBatchCount++; } } // Submit the final batch will all remaining vertices if (vertexBaseIndex > 0) { device.AddVertices( material, VertexMode.Quads, vertexData, vertexBaseIndex); submittedBatchCount++; } Profile.AddToStat(@"Duality\Stats\Render\Tilemaps\NumTiles", renderedTileCount); Profile.AddToStat(@"Duality\Stats\Render\Tilemaps\NumVertices", submittedTileCount * 4); Profile.AddToStat(@"Duality\Stats\Render\Tilemaps\NumBatches", submittedBatchCount); }
public TilemapChangedEventArgs(Tilemap tilemap, int x, int y, int width, int height) : base(tilemap) { this.pos = new Point2(x, y); this.size = new Point2(width, height); }
/// <summary> /// Runs the flood fill algorithm on the specified position and writes the result into the specified variables. /// </summary> /// <param name="tilemap"></param> /// <param name="pos"></param> /// <param name="preview">If true, the algorithm will cancel when taking too long for an interactive preview.</param> /// <param name="floodFillArea"></param> /// <param name="floodFillOrigin"></param> /// <returns>True, if the algorithm completed. False, if it was canceled.</returns> private bool GetFloodFillArea(Tilemap tilemap, Point2 pos, bool preview, Grid<bool> floodFillArea, ref Point2 floodFillOrigin) { Grid<Tile> tiles = tilemap.BeginUpdateTiles(); Point2 fillTopLeft; Point2 fillSize; bool success = FloodFillTiles(ref this.activeFillBuffer, tiles, pos, preview ? (128 * 128) : 0, out fillTopLeft, out fillSize); tilemap.EndUpdateTiles(0, 0, 0, 0); // Find the filled areas boundaries and copy it to the active area if (success) { floodFillOrigin = fillTopLeft; floodFillArea.ResizeClear(fillSize.X, fillSize.Y); this.activeFillBuffer.CopyTo(floodFillArea, 0, 0, -1, -1, floodFillOrigin.X, floodFillOrigin.Y); } return success; }
private void UpdateRigidBody(RigidBody body, int sectorX, int sectorY) { Sector sector = this.sectors[sectorX, sectorY]; // Determine collision checksum this.tempCollisionData.Clear(); int newChecksum = this.MergeCollisionData(sectorX, sectorY, this.tempCollisionData); // If it differs from our previous value, update collision shapes if (sector.Checksum != newChecksum) { // Clean up old shapes if (sector.Shapes != null) { foreach (ShapeInfo shape in sector.Shapes) { body.RemoveShape(shape); } sector.Shapes.Clear(); } else { sector.Shapes = new List <ShapeInfo>(); } // Generate new shapes { // Determine general working data Tilemap tilemap = this.referenceTilemap; Tileset tileset = tilemap != null ? tilemap.Tileset.Res : null; Vector2 tileSize = tileset != null ? tileset.TileSize : Tileset.DefaultTileSize; Point2 sectorBaseTile = new Point2( sectorX * SectorSize, sectorY * SectorSize); Vector2 sectorBasePos = sectorBaseTile * tileSize; // Clear the temporary edge map first this.tempEdgeMap.Clear(); // Populate the edge map with fence and block geometry AddFenceCollisionEdges(this.tempCollisionData, this.tempEdgeMap); AddBlockCollisionEdges(this.tempCollisionData, this.tempEdgeMap, sectorBaseTile, this.tileCount); if (this.solidOuterEdges) { AddBorderCollisionEdges(this.tempEdgeMap, sectorBaseTile, this.tileCount); } // Now traverse the edge map and gradually create chain / loop // shapes until all edges have been used. Rect localRect = Rect.Align(this.origin, 0, 0, this.tileCount.X * tileSize.X, this.tileCount.Y * tileSize.Y); GenerateCollisionShapes(this.tempEdgeMap, localRect.TopLeft + sectorBasePos, tileSize, this.roundedCorners, sector.Shapes); // Add all the generated shapes to the target body foreach (ShapeInfo shape in sector.Shapes) { body.AddShape(shape); shape.Friction = this.shapeFriction; shape.Restitution = this.shapeRestitution; } } sector.Checksum = newChecksum; } this.sectors[sectorX, sectorY] = sector; }