Skip to content

Commit 510d01c

Browse files
authored
Merge pull request #408 from C7-Game/pcen/use-tilemaps-render-units
Render Units on TileMap
2 parents 804dd49 + 4f920e5 commit 510d01c

File tree

11 files changed

+188
-180
lines changed

11 files changed

+188
-180
lines changed

C7/AnimationManager.cs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -90,19 +90,19 @@ public string getUnitFlicFilepath(UnitPrototype unit, MapUnit.AnimatedAction act
9090
// The flic loading code parses the animations into a 2D array, where each row is an animation
9191
// corresponding to a tile direction. flicRowToAnimationDirection maps row number -> direction.
9292
private static TileDirection flicRowToAnimationDirection(int row) {
93-
switch (row) {
94-
case 0: return TileDirection.SOUTHWEST;
95-
case 1: return TileDirection.SOUTH;
96-
case 2: return TileDirection.SOUTHEAST;
97-
case 3: return TileDirection.EAST;
98-
case 4: return TileDirection.NORTHEAST;
99-
case 5: return TileDirection.NORTH;
100-
case 6: return TileDirection.NORTHWEST;
101-
case 7: return TileDirection.WEST;
102-
}
10393
// TODO: I wanted to add a TileDirection.INVALID enum value when implementing this,
10494
// but adding an INVALID value broke stuff: https://github.com/C7-Game/Prototype/issues/397
105-
return TileDirection.NORTH;
95+
return row switch {
96+
0 => TileDirection.SOUTHWEST,
97+
1 => TileDirection.SOUTH,
98+
2 => TileDirection.SOUTHEAST,
99+
3 => TileDirection.EAST,
100+
4 => TileDirection.NORTHEAST,
101+
5 => TileDirection.NORTH,
102+
6 => TileDirection.NORTHWEST,
103+
7 => TileDirection.WEST,
104+
_ => TileDirection.NORTH,
105+
};
106106
}
107107

108108
public static void loadFlicAnimation(string path, string name, ref SpriteFrames frames, ref SpriteFrames tint) {
@@ -123,10 +123,9 @@ public static void loadFlicAnimation(string path, string name, ref SpriteFrames
123123
}
124124
}
125125

126-
public static void loadCursorAnimation(string path, ref SpriteFrames frames) {
126+
public static void loadCursorAnimation(string path, string name, ref SpriteFrames frames) {
127127
Flic flic = Util.LoadFlic(path);
128128
int row = 0;
129-
string name = "cursor";
130129
frames.AddAnimation(name);
131130

132131
for (int col = 0; col < flic.Images.GetLength(1); col++) {
@@ -247,4 +246,8 @@ public double getDuration() {
247246
double frameCount = flicSheet.indices.GetWidth() / flicSheet.spriteWidth;
248247
return frameCount / 20.0; // Civ 3 anims often run at 20 FPS TODO: Do they all? How could we tell? Is it exactly 20 FPS?
249248
}
249+
250+
public override string ToString() {
251+
return $"{unit.name}: {action}";
252+
}
250253
}

C7/AnimationTracker.cs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@ public struct ActiveAnimation {
2323

2424
public Dictionary<string, ActiveAnimation> activeAnims = new Dictionary<string, ActiveAnimation>();
2525

26-
public long getCurrentTimeMS()
27-
{
28-
return DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
29-
}
26+
public long getCurrentTimeMS() => DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
3027

3128
private string getTileID(Tile tile)
3229
{
@@ -110,19 +107,19 @@ public bool hasCurrentAction(MapUnit unit)
110107

111108
public void update()
112109
{
113-
long currentTimeMS = (! endAllImmediately) ? getCurrentTimeMS() : long.MaxValue;
110+
long currentTimeMS = !endAllImmediately ? getCurrentTimeMS() : long.MaxValue;
114111
var keysToRemove = new List<string>();
115112
foreach (var guidAAPair in activeAnims.Where(guidAAPair => guidAAPair.Value.endTimeMS <= currentTimeMS)) {
116113
var (id, aa) = (guidAAPair.Key, guidAAPair.Value);
117-
if (aa.completionEvent != null) {
114+
if (aa.completionEvent is not null) {
118115
aa.completionEvent.Set();
119116
aa.completionEvent = null; // So event is only triggered once
120117
}
121-
if (aa.ending == AnimationEnding.Stop)
118+
if (aa.ending == AnimationEnding.Stop) {
122119
keysToRemove.Add(id);
120+
}
123121
}
124-
foreach (var key in keysToRemove)
125-
activeAnims.Remove(key);
122+
keysToRemove.ForEach(key => activeAnims.Remove(key));
126123
}
127124

128125
public MapUnit.Appearance getUnitAppearance(MapUnit unit)

C7/Art/Title_Screen.jpg.import

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
importer="texture"
44
type="CompressedTexture2D"
5-
uid="uid://ds3dwrouk7g55"
5+
uid="uid://bkxkefpbld468"
66
path="res://.godot/imported/Title_Screen.jpg-067f940f7a89fae79632e2159c786062.ctex"
77
metadata={
88
"vram_texture": false

C7/C7Game.tscn

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,16 @@ libraries = {
257257
[connection signal="ShowSpecificAdvisor" from="." to="CanvasLayer/Advisor" method="OnShowSpecificAdvisor"]
258258
[connection signal="TurnEnded" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnEnded"]
259259
[connection signal="TurnStarted" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnStarted"]
260+
[connection signal="BlinkyEndTurnButtonPressed" from="CanvasLayer/GameStatus" to="." method="OnPlayerEndTurn"]
261+
[connection signal="toggled" from="CanvasLayer/SlideOutBar/SlideToggle" to="." method="_on_SlideToggle_toggled"]
262+
[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/DownButton" to="." method="_on_DownButton_pressed"]
263+
[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/RightButton" to="." method="_on_RightButton_pressed"]
264+
[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/LeftButton" to="." method="_on_LeftButton_pressed"]
265+
[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/UpButton" to="." method="_on_UpButton_pressed"]
266+
[connection signal="value_changed" from="CanvasLayer/SlideOutBar/VBoxContainer/Zoom" to="." method="onSliderZoomChanged"]
267+
[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/QuitButton" to="." method="_on_QuitButton_pressed"]
268+
[connection signal="pressed" from="CanvasLayer/ToolBar/MarginContainer/HBoxContainer/AdvisorButton" to="CanvasLayer/Advisor" method="ShowLatestAdvisor"]
269+
[connection signal="pressed" from="CanvasLayer/ToolBar/MarginContainer/HBoxContainer/UiBarEndTurnButton" to="." method="_onEndTurnButtonPressed"]
260270
[connection signal="BuildCity" from="CanvasLayer/PopupOverlay" to="." method="OnBuildCity"]
261271
[connection signal="HidePopup" from="CanvasLayer/PopupOverlay" to="CanvasLayer/PopupOverlay" method="OnHidePopup"]
262272
[connection signal="UnitDisbanded" from="CanvasLayer/PopupOverlay" to="." method="OnUnitDisbanded"]

C7/Game.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ enum GameState {
4444

4545
Stopwatch loadTimer = new Stopwatch();
4646
GlobalSingleton Global;
47-
private PlayerCamera camera;
47+
public PlayerCamera camera;
4848
bool errorOnLoad = false;
4949

5050
public override void _EnterTree() {
@@ -158,17 +158,17 @@ public void processEngineMessages(GameData gameData) {
158158
}
159159
}
160160

161-
// Instead of Game calling animTracker.update periodically (this used to happen in _Process), this method gets called as necessary to bring
162-
// the animations up to date. Right now it's called from UnitLayer right before it draws the units on the map. This method also processes all
163-
// waiting messages b/c some of them might pertain to animations. TODO: Consider processing only the animation messages here.
164-
// Must only be called while holding the game data mutex
161+
// updateAnimations updates animation states in the tracker and then their corresponding
162+
// sprites in the MapView. It must be called when holding the game data mutex.
163+
// TODO: before switching to tilemap, this was only called by the old UnitLayer _Draw
164+
// method. It only really needs to be called as frequently as animations update...
165165
public void updateAnimations(GameData gameData) {
166-
processEngineMessages(gameData);
167166
animTracker.update();
167+
mapView.updateAnimations();
168168
}
169169

170170
public override void _Process(double delta) {
171-
this.processActions();
171+
processActions();
172172

173173
// TODO: Is it necessary to keep the game data mutex locked for this entire method?
174174
using (var gameDataAccess = new UIGameDataAccess()) {
@@ -195,6 +195,8 @@ public override void _Process(double delta) {
195195
EmitSignal("ShowSpecificAdvisor", "F1");
196196
}
197197
}
198+
199+
updateAnimations(gameDataAccess.gameData);
198200
}
199201
}
200202

C7/Map/MapView.cs

Lines changed: 92 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System;
44
using Serilog;
55
using System.Linq;
6+
using System.Collections.Generic;
7+
using C7Engine;
68

79
namespace C7.Map {
810

@@ -34,17 +36,96 @@ public void toggleGrid() {
3436
private GameData data;
3537
private GameMap gameMap;
3638

37-
public override void _Draw() {
38-
GD.Print("draw...");
39-
game.animTracker.update();
40-
foreach ((string id, AnimationTracker.ActiveAnimation anim) in game.animTracker.activeAnims) {
41-
GD.Print($"{id}: {anim.ToString()}");
39+
private Dictionary<MapUnit, UnitSprite> unitSprites = new Dictionary<MapUnit, UnitSprite>();
40+
private CursorSprite cursor;
41+
42+
private UnitSprite spriteFor(MapUnit unit) {
43+
UnitSprite sprite = unitSprites.GetValueOrDefault(unit, null);
44+
if (sprite is null) {
45+
sprite = new UnitSprite(game.civ3AnimData);
46+
unitSprites.Add(unit, sprite);
47+
AddChild(sprite);
4248
}
43-
base._Draw();
49+
return sprite;
50+
}
51+
52+
private Vector2 getSpriteLocalPosition(Tile tile, MapUnit.Appearance appearance) {
53+
Vector2 position = tilemap.MapToLocal(stackedCoords(tile));
54+
Vector2 offset = tileSize * new Vector2(appearance.offsetX, appearance.offsetY) / 2;
55+
return position + offset;
4456
}
4557

46-
public override void _Process(double delta) {
47-
base._Process(delta);
58+
private void animateUnit(Tile tile, MapUnit unit) {
59+
// TODO: simplify AnimationManager and drawing animations it is unnecessarily complex
60+
// - also investigate if the custom offset tracking and SetFrame can be replaced by
61+
// engine functionality
62+
MapUnit.Appearance appearance = game.animTracker.getUnitAppearance(unit);
63+
string name = AnimationManager.AnimationKey(unit.unitType, appearance.action, appearance.direction);
64+
C7Animation animation = game.civ3AnimData.forUnit(unit.unitType, appearance.action);
65+
animation.loadSpriteAnimation();
66+
UnitSprite sprite = spriteFor(unit);
67+
int frame = sprite.GetNextFrameByProgress(name, appearance.progress);
68+
float yOffset = sprite.FrameSize(name).Y / 4f; // TODO: verify actual value
69+
Vector2 position = getSpriteLocalPosition(tile, appearance);
70+
sprite.Position = position - new Vector2(0, yOffset);
71+
Color civColor = new Color(unit.owner.color);
72+
sprite.SetColor(civColor);
73+
sprite.SetAnimation(name);
74+
sprite.SetFrame(frame);
75+
sprite.Show();
76+
77+
if (unit == game.CurrentlySelectedUnit) {
78+
cursor.Position = position;
79+
cursor.Show();
80+
}
81+
}
82+
83+
private MapUnit selectUnitToDisplay(List<MapUnit> units) {
84+
if (units.Count == 0) {
85+
return MapUnit.NONE;
86+
}
87+
MapUnit bestDefender = units[0], selected = null, interesting = null;
88+
MapUnit currentlySelected = game.CurrentlySelectedUnit;
89+
foreach (MapUnit unit in units) {
90+
if (unit == currentlySelected) {
91+
selected = unit;
92+
}
93+
if (unit.HasPriorityAsDefender(bestDefender, currentlySelected)) {
94+
bestDefender = unit;
95+
}
96+
if (game.animTracker.getUnitAppearance(unit).DeservesPlayerAttention()) {
97+
interesting = unit;
98+
}
99+
}
100+
// Prefer showing the selected unit, secondly show one doing a relevant animation, otherwise show the top defender
101+
return selected ?? interesting ?? bestDefender;
102+
}
103+
104+
public List<Tile> getVisibleTiles() {
105+
List<Tile> tiles = new List<Tile>();
106+
Rect2 bounds = game.camera.getVisibleWorld();
107+
Vector2I topLeft = tilemap.LocalToMap(ToLocal(bounds.Position));
108+
Vector2I bottomRight = tilemap.LocalToMap(ToLocal(bounds.End));
109+
for (int x = topLeft.X - 1; x < bottomRight.X + 1; x++) {
110+
for (int y = topLeft.Y - 1; y < bottomRight.Y + 1; y++) {
111+
(int usX, int usY) = unstackedCoords(new Vector2I(x, y));
112+
tiles.Add(data.map.tileAt(usX, usY));
113+
}
114+
}
115+
return tiles;
116+
}
117+
118+
public void updateAnimations() {
119+
foreach (UnitSprite s in unitSprites.Values) {
120+
s.Hide();
121+
}
122+
cursor.Hide();
123+
foreach (Tile tile in getVisibleTiles()) {
124+
MapUnit unit = selectUnitToDisplay(tile.unitsOnTile);
125+
if (unit != MapUnit.NONE) {
126+
animateUnit(tile, unit);
127+
}
128+
}
48129
}
49130

50131
private void initializeTileMap() {
@@ -127,7 +208,10 @@ private Vector2I stackedCoords(Tile tile) {
127208
public MapView(Game game, GameData data) {
128209
this.data = data;
129210
this.game = game;
211+
this.data = data;
130212
this.gameMap = data.map;
213+
cursor = new CursorSprite();
214+
AddChild(cursor);
131215
width = gameMap.numTilesWide / 2;
132216
height = gameMap.numTilesTall;
133217
initializeTileMap();
@@ -151,24 +235,6 @@ public MapView(Game game, GameData data) {
151235
foreach (Tile tile in gameMap.tiles) {
152236
updateTile(tile);
153237
}
154-
155-
// temp but place units in current position
156-
foreach (Tile tile in gameMap.tiles) {
157-
if (tile.unitsOnTile.Count > 0) {
158-
MapUnit unit = tile.unitsOnTile[0];
159-
UnitSprite sprite = new UnitSprite(game.civ3AnimData);
160-
MapUnit.Appearance appearance = game.animTracker.getUnitAppearance(unit);
161-
162-
var coords = stackedCoords(tile);
163-
sprite.Position = tilemap.MapToLocal(coords);
164-
165-
game.civ3AnimData.forUnit(unit.unitType, appearance.action).loadSpriteAnimation();
166-
string animName = AnimationManager.AnimationKey(unit.unitType, appearance.action, appearance.direction);
167-
sprite.SetAnimation(animName);
168-
sprite.SetFrame(0);
169-
AddChild(sprite);
170-
}
171-
}
172238
}
173239

174240
public Tile tileAt(GameMap gameMap, Vector2 globalMousePosition) {

0 commit comments

Comments
 (0)