PDA

View Full Version : Musings of a Tilemap Junky



Gleeok
07-05-2015, 08:40 AM
There's been a recent influx of programming related topics, and a lot of talk about OOP, so I thought I'd just share what I did today, which is say that "tiles are done like this, presto bango fix up the placeholder code and then move on." Yeah, I sort of had some placeholders in for different map stuff that I wanted to get rid of. The truth is that 2D tilemaps are very simple, like stupid simple. I've thrown together at least different versions over the years for different things. Usually just for fun when I'm prototyping an idea and want to see how it works with a whole graphics scene. Sure they are very basic, but more than enough for something like Pac Man or Breakout. Anyway, the point is that tilemaps are not hard by any means, and thinking you have to use a specialized 2D tile engine library is a little bonkers if you ask me. Anyway.

So back to to the point: If it is so easy why did I have all the placeholders in the first place? Which as it turns out is a totally good question by itself. So today I had the day off and just went ahead and solved the problem once and for all.

Well, I wanted it to support a bunch of features, I mean, RPG's are very complex. I hadn't ever made one before and I didn't want to screw anything up. When you think about what might need to have it can seem like it's complicated: You might have tiles that don't do anything, null tiles, static tiles, animated tiles, dynamically moving tiles, tiles that change into other tiles (eg; like combo->next). So when you start thinking about it from a code perspective you usually come up with something like this:




class Tile /*maybe : public MapEntity?*/
{
public:
virtual ~Tile() {}
virtual void Update() = 0;
virtual void Render() = 0;
virtual Rect GetCollisionRect() return Rect::Empty; }
//etc...
private:
int m_id; //reverse lookup
TileSet* m_parent; //needed for rendering
Region region;
//other stuff
};

class AnimatedTile
{
public:
void Update() { m_tileAnimation.Animate(); }
void Render { m_tileAnimation.RenderTile(this, this->GetParentTileSet()); }
//etc...
};

//ad nauseum...



Which basically solves every problem because now tiles can do everything we need them to do. ...right? ..wait. ..no. ...yes. ..? I mean it's grade A 80% lean choice OOP so... no. This what happens when you read c++ tutorials online people! What this actually is is an Entity system in disguise, and absolutely not a tilemap system. The whole point of tiles is they have a grid. If there is no grid, then you definitely shouldn't even have tiles at all. Plus this code is like 1000 bugs all waiting to strike, and another downside is that it is slow and very time consuming when all is said and done. I definitely do not want to spend 5 years getting it to work right with everything else in the game when they are added.

So I thought about what the actual problem is I came up with this: Tiles have static properties and also dynamic properties, but, tiles don't actually exist. That is, there is no such thing as a single tile at all. Tiles are part of a larger set, grouped by their image and perhaps animation--all tiles with the same id need to stay in sync, for example, and if the don't then you have problems--by layers, and groupings, and maybe even resembling larger objects. Tile groupings can even be scattered throughout the map, so you'd want to treat far away tiles similarly to nearby tiles instead of putting them to sleep like entities.

In the end I just ended up with the simplest solution. Which is mutable TileData, and immutable properties of each tile. Which is just this, minus some unfinished things:



enum TileAnimationType ENUM_TYPE(u8)
{
TileAnimationType_None,
TileAnimationType_Loop,
TileAnimationType_PingPong,
TileAnimationType_OneTime
};

enum TileAnimationFlags ENUM_TYPE(u8)
{
TileAnimationFlags_FlipX = 0x01,
TileAnimationFlags_FlipY = 0x02,
TileAnimationFlags_Reverse = 0x80,
};

struct RPG_API Tile
{
uint16 id; // depricated. (currently used in saving)
uint16 _padding;

/// The starting top-most rectangle position (in pixels) in the texture.
uint16 sourceX;

/// The starting left-most rectangle position (in pixels) in the texture.
uint16 sourceY;

/// The texture coordinates when rendering the tile.
Rectf uv;

/// The frame counter.
uint16 frameCounter;

/// The animation delay of each frame. (In fixed 16:1 point)
uint16 animationDelay;

/// The current animation frame.
uint8 currentFrame;

/// The maximum number of animation frames.
uint8 numFrames;

/// The type of animation.
uint8 animationType;

/// Animation flags. (Used internally)
uint8 animationFlags;

// aggregate


/// Updates the tile animation.
/// The tiles' texture information must be supplied along with the size of the tile.
void UpdateAnimation(float textureWidth, float textureHeight, uint32 tileWidth, uint32 tileHeight);

/// Sets the current animation frame and updates rendering data if needed.
/// Does not reset the frame counter.
void SetCurrentFrame(float textureWidth, float textureHeight, uint32 tileWidth, uint32 tileHeight, u8 frame);

FORCEINLINE bool IsAnimated() const { return numFrames > 1; }

static Tile Default;
};
CE_MAKE_TRAIT(Tile, is_pod);


And that's it, minus additional properties for gameplay mechanics. So simple, right? It may not solve every single problem you think might come up by itself, but it doesn't cause any either. Plus it can only be managed by components with specific knowledge about what it is they have to do with just straight forward, simple data. Speed goes way up and complexity way down. For example, when a map layer is told to render tiles it can cull and go through all the tiles in view at once, and simple throw all that data into an array, which renders the entire tile layer to the GPU in a single draw call. Can't get any faster than that. Actually, most layers will even share the same tileset so in that case every layer on screen can even be batched together... but that's really not necessary.


[edit] In case of possible name confusion, the tilemaps work like this:
-- Tileset->Tile array[]
-- TileMap->TileLayers[each layer holds a tileset pointer]->TileLayerCell array[]->also references a tile.

(so in ZC terms, the term 'tile' is like a 'combo', and 'tiles' are physical files like tilesheets or /png.)


[editmore] Ugh.. too tired to fix any grammer issues. That's grampers job.

Anarchy_Balsac
07-05-2015, 02:32 PM
Sometimes even simpler, for the Marauder Shields video game, the tile and item maps are one and the same (this made putting background tiles behind enemies and items challenging)

This is because the Platformer Starter kit I built it out of reads the entire thing as a text file matrix for each stage.

Gleeok
07-06-2015, 07:07 AM
Sometimes even simpler, for the Marauder Shields video game, the tile and item maps are one and the same (this made putting background tiles behind enemies and items challenging)

This is because the Platformer Starter kit I built it out of reads the entire thing as a text file matrix for each stage.

That is a little strange. Did it do that in the starter kit originally? I can't really see the benefit of that... :confuse:
Was it ever finished?




Anyway, back to the discussion about how optimizing early is bad and evil and all that stuff. Guess what I did today? Yep, you totally guessed it. :P

CPU side tilemap rendering is now 3x faster! (remember GPU is already maxed out basically) :) Since we are storing everything in tight arrays we have zero extra indirection or overhead compared to something like lists or maps so rewriting it to be fast was trivial. For some reason I couldn't get the profiler to check L2 cache hits or branch misses on this computer, but it seems like it's not an issue as everything is very compact already. Whatever. Stupid Microsoft.

At my max resolution of 1280x744 we've got 960 tiles on screen per layer, x2 layers = 1920 possible draw calls, which takes me 0.16 milliseconds, or, 1% of out the total target frame rate of 16.0 milliseconds, and was 3-7% CPU usage. With a normal sized resolution CPU usage was at 0%, and with a C# script over the top of it running a tetris game was maxed at 2%.

Keep in mind that and I've got a shitty laptop right now too. To put this in perspective for you guys: I can't actually play any game made in game-maker on this computer without major lag or fear of overheating, and even an rpgmaker vx game puts me at 80-90% CPU use.


At the end of the day you just have to feel good about some things, like not just throwing whatever crap you've got lying around into a bun and calling it a Big Mac, but actually creating something from scratch even though it takes a little longer, and really, just caring about things in general.

Saffith
07-06-2015, 10:10 AM
I don't think you're using the same definition of "optimization" as everyone else. Just designing things efficiently isn't really the same.
It sounds like you came up with the flyweight pattern.

Anarchy_Balsac
07-06-2015, 05:24 PM
That is a little strange. Did it do that in the starter kit originally? I can't really see the benefit of that... :confuse:
Was it ever finished?

Yes it was, I think they do that for simplicity. And yes, I finished it a long time ago, most people got it from the link on HoldTheLine.com. but since that site is dead, here's direct links to my google drive:

https://drive.google.com/open?id=0ByTIOLIV5IB0cWNpQncwYVFNUW8
https://drive.google.com/open?id=0ByTIOLIV5IB0d3RpVmlCeWprUDg

Gleeok
07-07-2015, 05:54 AM
It sounds like you came up with the flyweight pattern.

Interesting, I had to look up what a "flyweight" pattern was. :badrazz: I may not quite understand the concept correctly from the definition I saw though. To me "flyweight" just describes every program ever written in c, or just relegating the functionality of objects to the things that manage them. I don't know if that's right. ..?


I don't think you're using the same definition of "optimization" as everyone else. Just designing things efficiently isn't really the same.

Tell that to the stupid compiler. ;)

...
I don't have the original assembly file anymore but it was fairly retarded let me tell you what. Now I'm not very good at digesting that kind of stuff because I'm not very good at reading and understanding it in depth, but it was dumb. Obvious offenders were reading from too many points random memory that never changes, likely causing cache issues (Though like I said vcp didn't want to track them for some reason), and just having to pull out anything that was confusing to it from the inner loop. Pretty not-hard to do things, but it just seems like a waste a time. However in this case there was a large improvement so it was well worth it IMO.

I can post the newer code listing though. I did add a few good ideas in there.



; 255 :
; 256 : float32 tilePosX = (x1 * tileWidth) + (minX - x1) * tileWidth;

0005e 8b 45 10 mov eax, DWORD PTR _minX$[ebp]
00061 0f af 45 08 imul eax, DWORD PTR _tileWidth$[ebp]

; 257 : float32 tilePosY = (y1 * tileHeight) + (minY - y1) * tileHeight;
; 258 : float32 currentTilePositionX = tilePosX;
; 259 : float32 currentTilePositionY = tilePosY;
; 260 :
; 261 : Quad2D* vertexPointer = (Quad2D*)spriteBatch->PushCurrentVertexArrayPointer();

00065 8b 7d 0c mov edi, DWORD PTR _spriteBatch$[ebp]
00068 89 45 08 mov DWORD PTR tv870[ebp], eax
0006b 8b c6 mov eax, esi
0006d 0f af 45 fc imul eax, DWORD PTR _tileHeight$[ebp]
00071 db 45 08 fild DWORD PTR tv870[ebp]
00074 d9 5d 14 fstp DWORD PTR _tilePosX$[ebp]
00077 d9 45 14 fld DWORD PTR _tilePosX$[ebp]
0007a d9 5d ec fstp DWORD PTR _currentTilePositionX$[ebp]
0007d 89 45 08 mov DWORD PTR tv867[ebp], eax
00080 8b 47 28 mov eax, DWORD PTR [edi+40]
00083 89 45 18 mov DWORD PTR _vertexPointer$[ebp], eax
00086 db 45 08 fild DWORD PTR tv867[ebp]
00089 83 c4 30 add esp, 48 ; 00000030H

; 262 :
; 263 : spriteBatch->SetBlendMode(GetBlendMode());

0008c 8d 43 2c lea eax, DWORD PTR [ebx+44]
0008f 50 push eax
00090 8b cf mov ecx, edi
00092 d9 5d fc fstp DWORD PTR _currentTilePositionY$[ebp]
00095 e8 00 00 00 00 call ?SetBlendMode@SpriteBatch@@QAEXABVBlendMode@@@Z ; SpriteBatch::SetBlendMode

; 264 : spriteBatch->SetTextureID(GetTileset()->GetTextureID());

0009a 8b 43 14 mov eax, DWORD PTR [ebx+20]
0009d e8 00 00 00 00 call ?GetTextureID@Tileset@@QBEIXZ ; Tileset::GetTextureID
000a2 50 push eax
000a3 8b cf mov ecx, edi
000a5 e8 00 00 00 00 call ?SetTextureID@SpriteBatch@@QAEXI@Z ; SpriteBatch::SetTextureID

; 265 :
; 266 : const Color layerColor = GetColor();

000aa 8b 4b 30 mov ecx, DWORD PTR [ebx+48]

; 267 :
; 268 : for(int32 y(minY); y != maxY; ++y)

000ad 89 75 08 mov DWORD PTR _y$16981[ebp], esi
000b0 3b 75 1c cmp esi, DWORD PTR _maxY$[ebp]
000b3 0f 84 16 01 00
00 je $LN7@InternalDr@2

; 269 : {
; 270 : const TileMapLayerCell* currentCell = &m_tiles(y, minX);

000b9 8b 45 f0 mov eax, DWORD PTR _maxX$[ebp]
000bc d9 45 fc fld DWORD PTR _currentTilePositionY$[ebp]
000bf 2b 45 10 sub eax, DWORD PTR _minX$[ebp]
000c2 d9 45 ec fld DWORD PTR _currentTilePositionX$[ebp]
000c5 c1 e0 03 shl eax, 3
000c8 89 45 f0 mov DWORD PTR tv393[ebp], eax
000cb eb 02 jmp SHORT $LN9@InternalDr@2
$LN73@InternalDr@2:

; 267 :
; 268 : for(int32 y(minY); y != maxY; ++y)

000cd d9 c9 fxch ST(1)
$LN9@InternalDr@2:

; 269 : {
; 270 : const TileMapLayerCell* currentCell = &m_tiles(y, minX);

000cf 8b 43 24 mov eax, DWORD PTR [ebx+36]
000d2 0f af 45 08 imul eax, DWORD PTR _y$16981[ebp]
000d6 03 45 10 add eax, DWORD PTR _minX$[ebp]
000d9 8b 53 18 mov edx, DWORD PTR [ebx+24]
000dc 8d 14 c2 lea edx, DWORD PTR [edx+eax*8]

; 271 : const TileMapLayerCell* lastCell = currentCell + (maxX - minX);

000df 8b 45 f0 mov eax, DWORD PTR tv393[ebp]
000e2 03 c2 add eax, edx
000e4 89 45 ec mov DWORD PTR _lastCell$16986[ebp], eax

; 272 :
; 273 : for( ; currentCell != lastCell; ++currentCell)

000e7 3b d0 cmp edx, eax
000e9 0f 84 c3 00 00
00 je $LN54@InternalDr@2
$LN6@InternalDr@2:

; 274 : {
; 275 : const Tile* tile = currentCell->tile;

000ef 8b 02 mov eax, DWORD PTR [edx]

; 276 :
; 277 : if(tile != null)

000f1 85 c0 test eax, eax
000f3 0f 84 a6 00 00
00 je $LN74@InternalDr@2

; 278 : {
; 279 : Rectf textureCoords = tile->uv;

000f9 8d 70 08 lea esi, DWORD PTR [eax+8]

; 280 :
; 281 : // flip
; 282 : if(currentCell->flags & 1) Swap(textureCoords.min.x, textureCoords.max.x);

000fc 8a 42 06 mov al, BYTE PTR [edx+6]
000ff 8d 7d dc lea edi, DWORD PTR _textureCoords$16992[ebp]
00102 a5 movsd
00103 a5 movsd
00104 a5 movsd
00105 a5 movsd
00106 a8 01 test al, 1
00108 74 0c je SHORT $LN32@InternalDr@2
0010a d9 45 dc fld DWORD PTR _textureCoords$16992[ebp]
0010d d9 45 e4 fld DWORD PTR _textureCoords$16992[ebp+8]
00110 d9 5d dc fstp DWORD PTR _textureCoords$16992[ebp]
00113 d9 5d e4 fstp DWORD PTR _textureCoords$16992[ebp+8]
$LN32@InternalDr@2:

; 283 : if(currentCell->flags & 2) Swap(textureCoords.min.y, textureCoords.max.y);

00116 a8 02 test al, 2
00118 74 0c je SHORT $LN34@InternalDr@2
0011a d9 45 e0 fld DWORD PTR _textureCoords$16992[ebp+4]
0011d d9 45 e8 fld DWORD PTR _textureCoords$16992[ebp+12]
00120 d9 5d e0 fstp DWORD PTR _textureCoords$16992[ebp+4]
00123 d9 5d e8 fstp DWORD PTR _textureCoords$16992[ebp+12]
$LN34@InternalDr@2:

; 284 :
; 285 : float vertices[4] = {
; 286 : currentTilePositionX,
; 287 : currentTilePositionY,
; 288 : currentTilePositionX + floatTileWidth,
; 289 : currentTilePositionY + floatTileHeight
; 290 : };
; 291 :
; 292 : const Color color = layerColor;
; 293 :
; 294 : vertexPointer->SetVertexUVColorData((float32*)vertices, (float32*)&textureCoords, color);

00126 8b 45 18 mov eax, DWORD PTR _vertexPointer$[ebp]
00129 d9 45 f8 fld DWORD PTR _floatTileWidth$[ebp]
0012c d8 c1 fadd ST(0), ST(1)

; 295 : ++vertexPointer;

0012e 8b 7d 0c mov edi, DWORD PTR _spriteBatch$[ebp]
00131 d9 c2 fld ST(2)
00133 89 48 10 mov DWORD PTR [eax+16], ecx
00136 d8 45 f4 fadd DWORD PTR _floatTileHeight$[ebp]
00139 89 48 24 mov DWORD PTR [eax+36], ecx
0013c d9 ca fxch ST(2)
0013e 89 48 38 mov DWORD PTR [eax+56], ecx
00141 d9 10 fst DWORD PTR [eax]
00143 89 48 4c mov DWORD PTR [eax+76], ecx
00146 d9 cb fxch ST(3)
00148 83 c0 50 add eax, 80 ; 00000050H
0014b d9 50 b4 fst DWORD PTR [eax-76]
0014e 89 45 18 mov DWORD PTR _vertexPointer$[ebp], eax
00151 d9 45 dc fld DWORD PTR _textureCoords$16992[ebp]
00154 d9 58 b8 fstp DWORD PTR [eax-72]
00157 d9 45 e0 fld DWORD PTR _textureCoords$16992[ebp+4]
0015a d9 58 bc fstp DWORD PTR [eax-68]
0015d d9 cb fxch ST(3)
0015f d9 50 c4 fst DWORD PTR [eax-60]
00162 d9 ca fxch ST(2)
00164 d9 50 c8 fst DWORD PTR [eax-56]
00167 d9 45 dc fld DWORD PTR _textureCoords$16992[ebp]
0016a d9 58 cc fstp DWORD PTR [eax-52]
0016d d9 45 e8 fld DWORD PTR _textureCoords$16992[ebp+12]
00170 d9 58 d0 fstp DWORD PTR [eax-48]
00173 d9 c9 fxch ST(1)
00175 d9 50 d8 fst DWORD PTR [eax-40]
00178 d9 c9 fxch ST(1)
0017a d9 58 dc fstp DWORD PTR [eax-36]
0017d d9 45 e4 fld DWORD PTR _textureCoords$16992[ebp+8]
00180 d9 58 e0 fstp DWORD PTR [eax-32]
00183 d9 45 e8 fld DWORD PTR _textureCoords$16992[ebp+12]
00186 d9 58 e4 fstp DWORD PTR [eax-28]
00189 d9 58 ec fstp DWORD PTR [eax-20]
0018c d9 c9 fxch ST(1)
0018e d9 50 f0 fst DWORD PTR [eax-16]
00191 d9 45 e4 fld DWORD PTR _textureCoords$16992[ebp+8]
00194 d9 58 f4 fstp DWORD PTR [eax-12]
00197 d9 45 e0 fld DWORD PTR _textureCoords$16992[ebp+4]
0019a d9 58 f8 fstp DWORD PTR [eax-8]
0019d eb 02 jmp SHORT $LN3@InternalDr@2
$LN74@InternalDr@2:

; 267 :
; 268 : for(int32 y(minY); y != maxY; ++y)

0019f d9 c9 fxch ST(1)
$LN3@InternalDr@2:

; 272 :
; 273 : for( ; currentCell != lastCell; ++currentCell)

001a1 83 c2 08 add edx, 8

; 296 : }
; 297 :
; 298 : currentTilePositionX += floatTileWidth;

001a4 d9 c9 fxch ST(1)
001a6 d8 45 f8 fadd DWORD PTR _floatTileWidth$[ebp]
001a9 3b 55 ec cmp edx, DWORD PTR _lastCell$16986[ebp]
001ac 0f 85 3d ff ff
ff jne $LN6@InternalDr@2
$LN54@InternalDr@2:

; 267 :
; 268 : for(int32 y(minY); y != maxY; ++y)

001b2 ff 45 08 inc DWORD PTR _y$16981[ebp]

; 272 :
; 273 : for( ; currentCell != lastCell; ++currentCell)

001b5 dd d8 fstp ST(0)

; 299 : }
; 300 :
; 301 : currentTilePositionX = tilePosX;

001b7 d9 45 14 fld DWORD PTR _tilePosX$[ebp]
001ba 8b 45 08 mov eax, DWORD PTR _y$16981[ebp]

; 302 : currentTilePositionY += floatTileHeight;

001bd d9 c9 fxch ST(1)
001bf d8 45 f4 fadd DWORD PTR _floatTileHeight$[ebp]
001c2 3b 45 1c cmp eax, DWORD PTR _maxY$[ebp]
001c5 0f 85 02 ff ff
ff jne $LN73@InternalDr@2
001cb dd d9 fstp ST(1)
001cd dd d8 fstp ST(0)
$LN7@InternalDr@2:

; 303 : }
; 304 :
; 305 : // This will simply increment the current vertex pointer in the array.
; 306 : // Since we validate storage beforehand this is extremely fast.
; 307 : spriteBatch->PopCurrentVertexArrayPointer(vertexPointer);

001cf 8b 45 18 mov eax, DWORD PTR _vertexPointer$[ebp]
001d2 2b 47 28 sub eax, DWORD PTR [edi+40]
001d5 6a 50 push 80 ; 00000050H
001d7 59 pop ecx
001d8 99 cdq
001d9 f7 f9 idiv ecx
001db 6b c0 50 imul eax, 80 ; 00000050H
001de 01 47 28 add DWORD PTR [edi+40], eax


Good enough for me. Now I don't have to worry about it at all.

...There's probably not much more I can do anyway; too many FPU loads and stores going on.


[edit] Tip of the day: Don't ever trust the compiler to do things for you.

Saffith
07-07-2015, 12:37 PM
Interesting, I had to look up what a "flyweight" pattern was. :badrazz: I may not quite understand the concept correctly from the definition I saw though. To me "flyweight" just describes every program ever written in c, or just relegating the functionality of objects to the things that manage them. I don't know if that's right. ..?
Looking at it again, I think I misread that before. You're probably using the flyweight pattern, but that's not what you were describing.
It's basically deduplication. When you've got a lot of objects that are largely identical, don't give every one of them its own copy of the common data. Just keep one copy of each and give each instance a pointer. It's the same way combos work in ZC, for instance; each one on the screen is just a combo number rather than a separate copy of the definition.


Tell that to the stupid compiler. ;)
What I mean to say is that the "root of all evil" optimization isn't high-level design. It's stuff like rewriting a function in assembly to save a few clock cycles. Small things that make the code harder to understand and maintain for relatively little performance gain.

Gleeok
07-07-2015, 07:35 PM
Looking at it again, I think I misread that before. You're probably using the flyweight pattern, but that's not what you were describing.
It's basically deduplication. When you've got a lot of objects that are largely identical, don't give every one of them its own copy of the common data. Just keep one copy of each and give each instance a pointer. It's the same way combos work in ZC, for instance; each one on the screen is just a combo number rather than a separate copy of the definition.


What I mean to say is that the "root of all evil" optimization isn't high-level design. It's stuff like rewriting a function in assembly to save a few clock cycles. Small things that make the code harder to understand and maintain for relatively little performance gain.

I think I see what you mean though. [side note; awful explanation: https://en.wikipedia.org/wiki/Flyweight_pattern ]
It's like a chess program. Each piece has no information about itself, not even it's position. Then you get to the board, which defines all the pieces together as bit states. Then you have to up to a component that manages boards just to see if there's something at square 31, and so on. Very efficient.

Yep, ZC does do a lot of things well. Which is why I always think that rewriting it would be easy, because it's easy to see where the bare bones of it is very sane, and where it isn't.


There's probably many different definitions people have of what "optimization" is I guess. Trying to save a few cycles from a function that gets called 1000 times to me is stupid. Trying to stop potential L2 cache miss 1000 times on different particles, entities, and collision stuff is not stupid. That's just the way I see it.

Let's just put it this way:
I have a 1.8GHZ CPU which comes out to *roughly* 30,000,000 CYCLES/FRAME. If I was taking 3% of that, then optimized it down to 1% of that, then those come out to be an improvement of 20,000 CYCLES/FRAME or 1,200,000 CYCLES per SECOND.

....I wonder if I can SIMD that? ..hmm.

Samer
07-08-2015, 11:17 AM
I understood everything you said Gleeok.
I never tried doing tile editor programming before but it seems relatively difficult w/o a plan.

Arrays and hash tables, basically any ADT has no information about itself unless it's dynamically allocated properly.
I know you know (return by reference instead of value for relative faster accesses)

Gleeok
07-08-2015, 11:30 PM
I'm just glad no one noticed my totally and completely wrong hypothetical argument calculation of cpu cycles thing yet... whew, that was a close call.... >_> :P




Arrays and hash tables, basically any ADT has no information about itself unless it's dynamically allocated properly.
I know you know (return by reference instead of value for relative faster accesses)

Dynamic allocation is another good topic. That's another thing I realized I've been doing wrong this whole time (D'oh!). There should be zero allocation/deallocation (s) in a perfect system. I'm still working on it, but I'm confident that if/when it gets done that number will be zero.


[edit] Samer : A tile editor is actually very simple to make. It's basically just two scrollable panels in a window that let you use OpenGL to render what's inside. The rest is just rectangles and offsets. Simple addition and subtraction.The problem I have is:

1) I suck with layouts and UI stuff, and
2) I have trouble working with very large API's or tools like QT or wxWidgets, etc..

To actually put in the code that does something is easy for me, but reading through documentation and getting the editor to do what I want it to is extremely time consuming and frustrating. For example, it took me about 15 minutes to write this function that lets you pick out any amount of random tiles you want from an image, then add them to a tileset and create new files for them, but then it took 2 hours just to get the UI window to not crash. :mad: Here's another one: I stopped working on the editor completely because after adding a timer to it so I could show tiles animating at 60FPS, the OpenGL windows just start freaking out completely. No idea why, it wasn't documented. I finally just said" fuck it."

Samer
07-10-2015, 03:52 AM
I'm actually good at the stuff you have a time with for Qt

Gleeok
07-11-2015, 03:54 AM
I'm actually good at the stuff you have a time with for Qt

Want to make a tilemap editor? :nerd:


The old one I threw together was basically a
-TreeView (that didn't even work properly)
-Scrollable render panel with notebook tabs (map)
-Scrollable render panel with notebook tabs (tileset)
-other random things.

...and worked OK.

http://i235.photobucket.com/albums/ee138/Tiamat_AD/meff1.jpg

Isana
07-11-2015, 05:12 AM
Want to make a tilemap editor? :nerd:


The old one I threw together was basically a
-TreeView (that didn't even work properly)
-Scrollable render panel with notebook tabs (map)
-Scrollable render panel with notebook tabs (tileset)
-other random things.

...and worked OK.

Haha I'd be interested in helping out with this as well if I weren't as busy as I have been lately.
Solarus' new quest editor (https://github.com/christopho/solarus-quest-editor) (Qt based C++ as opposed to Java like the original) is quite nice; perhaps there's something to be learned from it that would assist the development
of FFC's map editor? I know you complained about the code he wrote for the Solarus engine itself before, but oh well haha.
http://i.imgur.com/AA2uTw5.png

At the very least, as usual, I'd really like to see internationalization support so that users would be able to contribute translations to different languages. Solarus' quest editor has this. (^_^;)

Tamamo
07-11-2015, 10:46 AM
GASP! My GUI sensors are off their radar. I would help with the GUI GLeeok. But I hate the purple stuff as much as you do.
Which is why the first RPG is called the Attack of the Purple Stuff. :)

Gleeok
07-11-2015, 09:10 PM
Solarus' new quest editor (https://github.com/christopho/solarus-quest-editor)


Aha! But does it have the most important thing you can possibly have, which is of course a toggle-able minimap display!? Aha! ... :finger:


...Yeah, QT would've been much better than wxWidgets...

Isana
07-18-2015, 03:25 PM
Aha! But does it have the most important thing you can possibly have, which is of course a toggle-able minimap display!? Aha! ... :finger:


...Yeah, QT would've been much better than wxWidgets...
Of course! How could I be so foolish?! One should never underestimate the almighty minimap! Nice work. (・∀・)

Yeah Qt is nice. I've tried wxWidgets, but I've never really used it that much. It does have the benefit of being a fair bit more lightweight though haha.