/agdg/ - Amateur Game Development General

Just Like Make Game

(You probably don't need to) SAVE THIS FILE (any more): Anon.cafe Fallback File v1.1 (updated 2021-12-13)

Anon.cafe will shut down as of 00:00 UTC on 15 March 2024. Announcement here.

Max message length: 20000

Drag files to upload or
click here to select them

Maximum 5 files / Maximum size: 20.00 MB

Board Rules
More

(used to delete files and postings)


Open file (3.20 KB 920x512 raylib.png)
Open file (126.94 KB 800x600 losdiabolol2.jpg)
Open file (117.51 KB 1000x750 sd2.jpg)
Open file (97.06 KB 768x576 ff12.jpeg)
ARPG project with Raylib yesdev 03/30/2023 (Thu) 03:39:45 No.486
Greetings and salutations, faggots. I'm currently working on a 2D, isometric-perspective action RPG. The goal is a Diablo-like, minus the Skinner-box loot pinata bullshit and adding a party system with simple but configurable AI party members - think Seiken Densetsu 2, 3 and Final Fantasy XII. I'm using C++ with Raylib as my primary graphics/input/output library, no "game engine" to speak of as yet. As I'm going through a bit of a refactor, I thought I would re-organize the project from scratch and document it here in the form of a tutorial slash dev blog. I'd like to use it to dump progress updates and code snippets, as well as for it to be a place to discuss Raylib and general RPG development. If you follow along, you should be able to build a similar game without too much difficulty. Disclaimer though - I am not a pro (game developer, I do a different kind of engineering for my day job), and am new to C++, so don't expect perfect code, best design practices, or anything like that. Stay tuned for an intro to Raylib, my project/build system, and design document.
>>533 I recognize Blender, but what's that other GUI you're using?
>>534 Tiled my friend. ht tps://www.mapeditor.org/
>>535 Appreciated.
Open file (476.26 KB 1920x1047 lights.png)
Open file (243.24 KB 1284x758 zoom.png)
Open file (103.66 KB 1284x758 zoom_in.png)
Open file (110.35 KB 1284x758 zoom_out.png)
>>533 Busy weekend, no time to do any code cleanup or a write-up for it but, I got the map loader going again, plus an ambient light level setting and placeable lights in the editor (next up is static light calculation), the map displayed in game, plus zooming in and out with the scroll wheel. Proper update soon.
Open file (302.96 KB 1284x758 static.png)
>>538 Static lighting was attempted. See if you can spot all the errors.
Open file (769.04 KB 1608x1276 d2_light0.png)
Open file (399.83 KB 1608x1276 d2_light2.png)
Open file (448.78 KB 1608x1276 d2_light3.png)
Open file (371.31 KB 1608x1276 d2_light4.png)
Open file (312.08 KB 1608x1276 d2_light5.png)
>>543 Let's talk lighting some more. So I fired up the ol' D2 again the other day and played around for a couple of hours, checking how shit works. While the gameplay puts me to sleep, technically it's a very impressive game. While I did like the animated water and rain drops effect on the river around the camp, I want to focus on the lighting right now. Diablo 2 employs a day-night cycle, which is cool. At night, and inside caves and crypts and such, the game is dark, like pitch-black outside of the player's light radius. In addition to the player's light source, there are also torches and fires, as well as light given off by certain enemies. As you can see in the pics, it's rather smooth; no real gradient banding, blockiness, or the 'starburst' effect you get with vertex lighting sometimes. There appear to be two 'layers' to the lighting effect. The first is what I assume to be an overlay, a "fog of war"-like low-resolution black texture overlayed over the screen, with the alpha channel value the inverse of the light level at the tile it covers. The second, which you can see in the last 3 pics, is what appears to be something like vertex lighting, where objects with some height to them, like pillars and stalagmites, block light and project a cone of shadow behind them, calculated in realtime as the player moves around them. I want to say that it's vertex lighting, or something similar, because I've seen some quite thin ones, like the ones cast by single leaf-less trees outdoors, thinner than I think can be achieved by a grid or block-based approach. It comes with some artifacts though, as you can see with the stalagmite in the last pic. I'd like to see if I can't get similar effects in my game.
Open file (140.74 KB 1204x838 blurry_light0.png)
Open file (48.21 KB 1204x838 blurry_light2.png)
Open file (69.99 KB 1204x838 blurry_light1.png)
>>546 A first attempt, done in the usual top-down testbed. The first pic is a 24x16 (the size of the screen in tiles) black texture, which the alpha channel of each pixel based on the underlying tile's illumination level. This texture is then rendered over the screen, with bilinear filtering to provide the blur effect. As you can see, it's not the greatest, there are some artifacts, like areas that should be 100% occluded being partially lit. That said, the banding and discrete light levels of the previous attempts are gone, see second pic. The third pic shows the result of combining this technique, plus the previous one (using tile luminance value to tint rendered texture for that tile). Combining them just results in the shadows "creeping out" into the lit spaces a bit. It's not the worst, but still isn't quite what I'm going for. I can still see the effect being useful for a fog of war, or to cover up not-yet-revealed map tiles. Technique used in this post taken from raylib 'fog of war' example: ht tps://www.raylib.com/examples/textures/loader.html?name=textures_fog_of_war Next, I'd like to see if the shadow overlay can be used to color the scene as well, to provide colored lighting and shadows, versus the black-only Diablo uses, using the texture blend modes shown off in this example: ht tps://www.raylib.com/examples/textures/loader.html?name=textures_blend_modes Maybe >>508 anon could weigh in some. Come up with a decent lighting solution yet?
Open file (158.71 KB 518x468 ClipboardImage.png)
Open file (766.28 KB 1000x500 dirt roads shitty.png)
Open file (106.89 KB 1097x714 ClipboardImage.png)
First of all, yours looks fine, and you might consider changing it after you have other assets, to see how it all fit. I suspect that each tile n diablo is actually 9 tiles in terms of lighting, like picrelated. (I am planning to make roads that way). And resulting brightness/color/ overlay is selected based on their values. Instead of global on screen gradient overlay >>508 dark mask.png I wanted to use here. (unless its simply opengl thing, but they dont always render with opengl, and these days its hard to tell what is used) For example you said "this pillar is wrong". Its 3rd picrelated. Only 5 is dark in that tile, but since big tile to the left is completely dark, it considers small tiles 147 to be dark too. But 236 are bright, because they dont have vision blocking. And ground tiles likely use "this is vision blocking pillar". I think I have... yep, here it is http://paul.siramy.free.fr/_divers/dt1_doc/ almost complete explanation on how everything works in d2. And in diablo 1 they used palette for brightness. Simply -10 to brightness could result in next tier of darkness for a tile (if palette is a set of 10 colors repeating with lower brightness N times). Its actually the fastest and least resource intense solution, however it involved a lot of work to make it. I am planning on making app for managing tilesets, cropping and such, and I could technically bake few darkness levels that way, with just a "cycling" palette. I dont really need many colors without proper shaders. You can also make animations with cycling palettes, I think its quite easy with sdl. However, its either animation or brightness, unless you want to make chimera by combining both, instead of just making it normally. And "easy" as in making separate software to pick colors for animations, without losing too much image quality. In diablo 1 they picked colors for the whole tileset, and divided 128 colors for characters and 128 colors for ground/wall tiles. And they regretted giving too many colors for walls/ground. However in modern sdl, I am 95% sure you can have separate palette for each texture, and even its own number of colors. However its way more labor intense and I likely use global onscreen overlay and simple -10RGB as an easy solution. Palette is really versatile tool, but its just too hard for me to manage. Also character shadow is just his character image(made out of separate parts, like legs, torso arms...), turned dark and rotated a little + transparency. You can actually see how some overlaps of player parts create darker parts of the shadow. Everything you need to know about d2 tiles. http://paul.siramy.free.fr/_divers/dt1_doc/
>>548 PS. with 32x32 tile size, you dont really need dark overlay, I guess. It probably would looks smooth enough as it is, with just color(palette) modulation per tile(texture). Or maybe they use it too, but I doubt it.
Open file (598.65 KB 1367x957 collision1.png)
Open file (304.66 KB 1396x964 collision2.png)
>>548 Thanks for the good write-up about Diablo's tiles. I didn't know that they were using a 5x5 grid of sub-tiles in the lighting and visibility calculations, but it makes matches up with my observations. Cool site, I sometimes forget how they used to really pack the data in on older games, to fit on a CD or two or whatever. Custom binary data formats for everything, run length encoded compression, and on and on. Neat tech, and a lot of brilliant solutions out there but that's not for me at the moment; I'm trying to keep things as simple as I can for now. >(I am planning to make roads that way) By that, do you mean that your roads won't simply be cosmetic, but that you'll actually do some sort of collision check for whether you're on/off the road and have it impact gameplay? (Movement speed, etc.) >Also character shadow is just his character image(made out of separate parts, like legs, torso arms...), turned dark and rotated a little + transparency. This I knew. I think it also explains how torches and fire sprites cast shadows where they shouldn't, it's just using the opaque part of the texture as a shadow. I do like this sub-tile shadow-casting technique though. It makes me wonder if I'm going to have to abandon Tiled as a level editor, since there doesn't seem to be support for different tile size/numbers on a per-layer basis. My current level format has a 'collision' layer, but it's just fully passable/fully blocked at the moment, no sub-tile collision. Here's a couple of shots of what that looks like. It wouldn't be hard though, to make a 'collision editor' that loads and displays a level, minus fancy lighting and such, that creates a grid 5X the resolution of the map, and lets you paint collision/collusion on top of the level and saves it to a simple XML file. I might give it a try one of these days.
>>550 >and lets you paint collision/collusion on top of the level Couldn't you store collision for each tile as metadata? Then you wouldn't need to manually paint it.
Open file (10.30 MB 4000x2000 dirt tiles.png)
Open file (61.88 KB 480x240 flower 200x100.png)
Open file (20.51 KB 100x108 freeb_01.png)
>>550 >roads >By that, do you mean that your roads won't simply be cosmetic, but that you'll actually do some sort of collision check I mean, I would use same way I calculate light on tiles. Instead of "this is crossing +, this is straight horizontal road -", I would have subtiles with "this is road" and draw lines based on that. Same with walls, so instead of "this is corner <", I will use "this is wall, above it is another wall, and to the right is a wall, so this drawn as corner wall". So for a wall, yeah, they can serve as collision check. >abandon tiled I think you should just use same tile format as you are right now, and just save textures differently in engine. I am actually making (soon™) tool to turn picrelated (big high res image, probably uncropped and unmasked, or stuff like grass going over tile borders and I want to keep some of it) and for player/npc tiles, which will downscale (downscaling gives much better image quality compared to just rendering in regular resolution) and cropping empty image parts, and saving metadata json, with offsets, png locations, animaton frames, replacing transparency with colorkey, etc. Essentially almost exactly the same tool they use in brigador, it comes with installation, you should check it out. I also want to make tool for palette manipulation/packing to save space, but it sounds really hard, since I have to check if everything looks alright after I decimated the palette, which sounds like a nightmare to process, and I am not well versed in image color manipulation at all. But I am afraid, it might be necessary for smooth (acceptably smooth, dont need perfect) shadows. I looked up available image editors, and tools for batch editing, but most of them have flaws which make them unusable to me. >checked it out myself >see based on SpriteSheetPacker https://spritesheetpacker.codeplex.com/ https://github.com/walteryoung/SpriteSheetPacker at the bottom I should check it out myself. And btw, consider allowing players to see monsters behind the pillars. Otherwise it might look like they appear out of nowhere, even if tile was quite bright and right next to them.
Open file (53.13 KB 1507x882 ex1.png)
Open file (48.04 KB 1506x879 ex2.png)
Open file (47.42 KB 1500x882 ex3.png)
Open file (31.87 KB 1501x879 ex4.png)
Open file (58.28 KB 1506x882 ex5.png)
>>551 >Couldn't you store collision for each tile as metadata? Sure, ideally. Right now though, I'm only using grid-aligned tiles and objects, so it's easy to just bucket fill the map as passable, then draw on top of the wall/object tiles. In the near future I'll be placing things at arbitrary locations - decorative bits, interactive objects, etc. In that case, I'd like to try my hand at at a system like diablo 2's, where there is a grid-aligned visible/walkable array at 5x or so the native tile resolution. See this mockup for an example of what I'm talking about. You'd have an object placed on the map at some arbitrary coordinates, in this case, a couple of shitty crates with half-assedly drawn shadows. With that object, there would be an associated 'blocking' layer or separate image, for path-finding or whatnot. This would then be pre-processed at some point, to "rasterize" it to the underlying grid. That grid would then be used in game for whatever purpose and the image itself used only for drawing at that point. >Then you wouldn't need to manually paint it. Sometimes some things are just faster to do by hand when prototyping. It's hacky for sure, but see above for what I'd like to do eventually.
Open file (134.65 KB 1415x879 wallmock1.png)
Open file (43.21 KB 1415x882 wallmock2.png)
Open file (117.41 KB 1409x878 wallmock3.png)
Open file (111.13 KB 1413x885 wallmock4.png)
>>554 Another shitty GIMP mock-up showing how it would work with macro-grid (tile-sized) aligned wall tiles and doors.
Open file (669.81 KB 384x1728 t_alltiles.png)
>>555 You can probably save yourself the trouble and make doors same path blocking as the walls, because why bother? Same with small (smaller than "full" tile) objects, just make them destructible, when someone walks over them, and ignore them for pathing. Sure, they still need collision, but just make it full tile size. Players will enjoy breaking objects by walking alone. I guess, alternatively, you can generate maps with random path blockers, and just place whichever object fits this "hole". But its probably not worth the trouble, even if just a minor addition to map generation. Sure, objects that are not at 45/90 degree angles are really fucking fancy, but I will not be surprised if autists would prefer proper 45/90 degree angles. It just looks more orderly.
Open file (44.04 KB 253x120 out scaled.png)
Open file (56.08 KB 253x120 outscaled2.png)
Open file (2.85 MB 1900x900 out 1.png)
Open file (4.01 MB 1900x900 sss.png)
I wonder if I should worry that 50% of tile textures are empty. (also I feel like I had this conversation before) I can do what diablo did, but that involves making my own renderer, which is obviously not an option. Or editing SDL2 to save stuff like it described here http://paul.siramy.free.fr/_divers/dt1_doc/ . I read that "empty(including the ones which are excluded by color key, usually(255,0,255) pixels are rendered very fast" so I dont really need to worry that textures have a bunch of zeros in them... I guess "dont optimize too early" is a rule for a reason, however I do want to avoid remaking everything later. However doing shading just by modulating texture and rendering it in smaller chunks will totally work for smooth shadows. It will look slightly better than picrelated (out scaled.png) . And apparently I can actually use it to make perspective effect (but I doubt I ever will). Still, cool option to have and consider. I can have Z levels on completely flat map, just by having sharp difference in brightness. Even diablo 2 (20 years ago on software renderer) didnt have this amazing technology.
Open file (1.85 MB 1900x900 out 16l.png)
Open file (1.71 MB 1900x900 out 8l.png)
Open file (1.73 MB 1900x900 out 8l 96x48.png)
Even with 8 levels of brightness, unadjusted with the exact same -32/256 shift, it looks fine. And with 16 levels of brightness it is basically perfect gradient, for 32x16 tiles. 8levels with 96x48 tiles, with slightly adjusted shift should look fine, in case this lighting + too many render calls would be slow. But I doubt it.
Open file (4.78 KB 128x256 goblin16.png)
Open file (30.84 KB 804x638 palette1.png)
Open file (31.31 KB 804x638 palette2.png)
>>553 I am going to stick with Tiled for now but, I may end up putting some custom tooling between the map and tileset files output by Tiled and the actual game. Looks like you might end up going the same route. >palettes I have not yet looked into palette manipulation but it is a mainstay of games, especially palette-swapped enemies. Raylib has a bunch of image-manipulation functions and from what I can tell, shouldn't be too hard to palette-swap. It looks like you can get a pointer to an array of colors (palette) of an image Actually I just went ahead and did it to see if it would work. Turns out the Raylib functions Color * palette = LoadImagePalette(img, total_colors, &color_count); and ImageColorReplace(&img, palette[i], new_palette[i]); did the trick. Pardon the shitty new palette, I just adjusted the hue of the original colors in gimp and took the rgb value from there. >And btw, consider allowing players to see monsters behind the pillars. Otherwise it might look like they appear out of nowhere, even if tile was quite bright and right next to them. If I recall correctly, that would happen to me a lot in the first Diablo. Usually large monsters on the tile next to a doorway (on the other side of a wall), they just suddenly pop in when moving to the tile next to the door. That shouldn't be a problem for me, since monsters are drawn whether they're visible or not. They should naturally "stick out" and be visible if they're physically larger than the thing occluding them.
>>559 P.S. When working in GIMP and you want to palettize (sp?) a PNG image with 1-bit alpha transparency, go to image -> mode -> Indexed and select one less color than you want, since it doesn't seem to count the transparent one. In my case, I scaled down to 128x256 and picked "15 colors" for this goblin I picked up somewhere. I haven't yet tried with images with semi-transparency where you would need 8-bit alpha though.
Open file (12.58 KB 1284x758 fps0.png)
Open file (139.82 KB 1284x758 fps1.png)
Open file (92.00 KB 1506x855 levelchange0.png)
Open file (131.33 KB 1500x855 levelchange1.png)
Open file (108.09 KB 1500x854 levelchange2.png)
>>557 I see you got some lighting in, cool. >I read that "empty(including the ones which are excluded by color key, usually(255,0,255) pixels are rendered very fast" This is probably true. I'm sure, given the prevalence of alpha transparency in games programming, someone at some point, put in a if (pixel == 0) { return; } else { actually_render(pixel); } somewhere deep in the OpenGL texture pixel rasterizing code. I mean, look at my screenshots; I really wouldn't worry about it. This is running on a micro PC the size of a sandwich with some ultra-low power Ryzon processor too by the way. I am absolutely targeting potatoes with integrated GPUs with my game. >I guess "dont optimize too early" is a rule for a reason, however I do want to avoid remaking everything later. Sure. There's a difference between "early optimization" and "planning ahead", though, which is why I do all the little experimentation that I do. You also have to consider that not all "optimizations" are really improvements, if they complicate code or asset creation for little improvement in gameplay or engine performance or whatever. >However doing shading just by modulating texture and rendering it in smaller chunks will totally work for smooth shadows. Are you refering to the screens in >>558 ? First thing I thought looking at those was how much of a bitch it would be to work at that grid size, the sheer number of tiles to place for a level of any playable size. I hadn't thought of taking the texture for a tile and mapping it 4 or more quads with their brightness and hue taken from a grid of higher resolution than the tile grid. I might have to steal that one. >I can have Z levels on completely flat map Yeah, there are a bunch of different ways to do that. >just by having sharp difference in brightness. That I'm not quite seeing. I only see a difference in brightness; it isn't giving me any illusion of curvature or height/depth. You would probably be better served with a more traditional approach like I sketched up in pics 3-5.
Open file (64.75 KB 804x638 gobs.png)
Open file (591.00 B 16x1 blue_pal.png)
Open file (591.00 B 16x1 red_pal.png)
Open file (4.78 KB 128x256 goblin16.png)
>>559 OK, I got it to where you can load a palette file and apply it to an image, done messing with this for a while. If you want to check it out, just set up your folders like I did above and drop these images in 'data'. main is as follows, if it'll let me post it all: // main.cpp #include "main.h" #include "raylib.h" #include "debug.h" #include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 void DrawTextureSimple(Texture2D & texture, Vector2 origin, Color tint, float scale) { // Check if texture is valid if (texture.id > 0) { float width = (float)texture.width * scale; float height = (float)texture.height * scale; rlSetTexture(texture.id); rlBegin(RL_QUADS); rlColor4ub(tint.r, tint.g, tint.b, tint.a); rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer // Top-left corner for texture and quad rlTexCoord2f(0.0f, 0.0f); rlVertex2f(origin.x, origin.y); // Bottom-left corner for texture and quad rlTexCoord2f(0.0f, 1.0f); rlVertex2f(origin.x, origin.y + height); // Bottom-right corner for texture and quad rlTexCoord2f(1.0f, 1.0f); rlVertex2f(origin.x + width, origin.y + height); // Top-right corner for texture and quad rlTexCoord2f(1.0f, 0.0f); rlVertex2f(origin.x + width, origin.y); rlEnd(); rlSetTexture(0); } } int main(int argc, char *argv[]) { /// ################ SETUP BEGIN ################ SetWindowState(FLAG_MSAA_4X_HINT); SetWindowState(FLAG_VSYNC_HINT); //SetTargetFPS(300); InitWindow(WIN_WIDTH, WIN_HEIGHT, "PALETTE SWAP"); // default to windowed size on creation center_window(); int center_x = GetScreenWidth() / 2; /// texture and palette stuff // load base goblin image Image img = LoadImage("data/goblin16.png"); // resize goblin and convert to GPU texture ImageResizeNN(&img, 256, 512); Texture2D green_gob = LoadTextureFromImage(img); int total_colors = 16; int color_count = 0; // get the palette for the base goblin image Color * palette = LoadImagePalette(img, total_colors, &color_count); // load red palette image file Image pal_img = LoadImage("data/red_pal.png"); // get palette from palette image Color * red_palette = LoadImagePalette(pal_img, total_colors, &color_count); // replace the colors in the base goblin image with the ones in the palette (image in CPU memory) for (int i = 0; i < total_colors; i++) { ImageColorReplace(&img, palette[i], red_palette[i]); } // resize goblin and convert to GPU texture ImageResizeNN(&img, 256, 512); Texture2D red_gob = LoadTextureFromImage(img); // load base goblin image again since we overwrote it with the red palette data img = LoadImage("data/goblin16.png"); // load blue palette image file pal_img = LoadImage("data/blue_pal.png"); // get palette from palette image Color * blue_palette = LoadImagePalette(pal_img, total_colors, &color_count); // replace the colors in the base goblin image with the ones in the palette (image in CPU memory) for (int i = 0; i < total_colors; i++) { ImageColorReplace(&img, palette[i], blue_palette[i]); } // resize goblin and convert to GPU texture ImageResizeNN(&img, 256, 512); Texture2D blue_gob = LoadTextureFromImage(img); /// have to unload palette data then /// ################ SETUP END ################ // Enter main game loop while (!WindowShouldClose()) // Until we detect window close button or exit command { /// ################ UPDATE BEGIN ################ /// ################ UPDATE END ################ /// ################ DRAWING BEGIN ################ BeginDrawing(); ClearBackground(BLACK); // draw the three color goblins DrawTextureSimple(red_gob, {0, 0}, WHITE, 1.0f); DrawTextureSimple(green_gob, {center_x - 128, 0}, WHITE, 1.0f); DrawTextureSimple(blue_gob, {GetScreenWidth() - 256, 0}, WHITE, 1.0f); DrawFPS(20, 20); // draw red palette for (int i = 0; i < total_colors; i++) { // draw red palette DrawRectangle(i * 16, 550, 16, 16, red_palette[i]); DrawRectangleLines(i * 16, 550, 16, 16, WHITE); // draw base palette DrawRectangle((center_x - 128) + i * 16, 550, 16, 16, palette[i]); DrawRectangleLines((center_x - 128) + i * 16, 550, 16, 16, WHITE); // draw blue palette DrawRectangle((GetScreenWidth() - 256) + i * 16, 550, 16, 16, blue_palette[i]); DrawRectangleLines((GetScreenWidth() - 256) + i * 16, 550, 16, 16, WHITE); } EndDrawing(); /// ################ DRAWING END ################ } // Cleanup CloseWindow(); // Close window and delete OpenGL context } void center_window(void) { int display = GetCurrentMonitor(); int display_width = GetMonitorWidth(display); int display_height = GetMonitorHeight(display); SetWindowPosition((display_width / 2) - (GetScreenWidth() / 2), (display_height / 2) - (GetScreenHeight() / 2)); }
>>558 >If I recall correctly, that would happen to me a lot in the first Diablo. Usually large monsters on the tile next to a doorway (on the other side of a wall), they just suddenly pop in when moving to the tile next to the door. That shouldn't be a problem for me, since monsters are drawn whether they're visible or not. They should naturally "stick out" and be visible if they're physically larger than the thing occluding them. I was worried about half-lit tiles which look like player can see what is inside of them, but in actuality everything in that tile is invisible. >That I'm not quite seeing. I only see a difference in brightness; it isn't giving me any illusion of curvature or height/depth Really? For me >>558 these look like obvious layers floating over each other. But I did mean it more like a joke, however having additional detail level, basically for free is a neat thing to have. Also I haven't figured out how to render cliffs which would look good enough. In fact brigador uses similar system (I think), with depth image textures, unless they use shaders and its just like 1d normal map. >too many tiles I though of that too. Its 4k ground tiles per screen, which is equivalent of rendering 4k pixels with software renderer, which is not that many. Worst case, I can reduce it by a lot by using 96*48 tiles, like in last picture here >>558 . I can also use grid of just 9 subtiles, but it seems like a bad idea, visually. Its not that much data, to store or even generate on the fly. I mean, it worked for diablo2, and subtile data is probably just an additional byte per subtile, either stored or generated. Even assuming full size tile is 32 bytes of map data, and 4k (32*16) tiles per screen, and with map size of 80 screens, its only 10 mb of data. Might be a bit annoying to wrap a head around it, but it should be fine.
Open file (34.66 KB 804x638 pp0.png)
Open file (35.39 KB 804x638 pp1.png)
Open file (35.83 KB 804x638 pp2.png)
Open file (12.20 KB 128x128 crate.png)
Open file (4.78 KB 128x256 goblin16.png)
>>562 Implemented one of the vertical slice/proof-of-concept features today - pixel-perfect object selection. The selection is done in two steps: the first is to see if the mouse click coordinates are within an object's axis-aligned bounding box (AABB), for which I'm just using the texture size for right now (eventually I will use a per-image settable AABB, to reduce the number of pixel-selected searches, since they are far more expensive. The second step is to load the image data from the GPU texture back into main RAM, then query the pixel at the click location to see if it's alpha channel value is higher than 0 - in other words, is not a transparent pixel. When an object is selected, I then use a shader (taken unedited from Raylib example ht tps://github.com/raysan5/raylib/blob/master/examples/shaders/shaders_texture_outline.c) to draw an outline around it. main.cpp is as follows: // main.cpp #include "main.h" #include "raylib.h" #include "debug.h" #include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 void DrawTextureSimple(Texture2D & texture, Vector2 origin, Color tint, float scale) { // Check if texture is valid if (texture.id > 0) { float width = (float)texture.width * scale; float height = (float)texture.height * scale; rlSetTexture(texture.id); rlBegin(RL_QUADS); rlColor4ub(tint.r, tint.g, tint.b, tint.a); rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer // Top-left corner for texture and quad rlTexCoord2f(0.0f, 0.0f); rlVertex2f(origin.x, origin.y); // Bottom-left corner for texture and quad rlTexCoord2f(0.0f, 1.0f); rlVertex2f(origin.x, origin.y + height); // Bottom-right corner for texture and quad rlTexCoord2f(1.0f, 1.0f); rlVertex2f(origin.x + width, origin.y + height); // Top-right corner for texture and quad rlTexCoord2f(1.0f, 0.0f); rlVertex2f(origin.x + width, origin.y); rlEnd(); rlSetTexture(0); } } class PickableObject { public: std::string name; Texture2D texture; Shader outline_shader; Vector2 center; bool within_aabb = false; bool selected = false; // constructor PickableObject(std::string name, std::string texture_file, Vector2 center); // class methods void draw(void); void check_collision(Vector2 mouse_coords); }; PickableObject::PickableObject(std::string name, std::string texture_file, Vector2 center) { // set name this->name = name; // set position this->center = center; // load image #ifdef DEBUG debug_out("ATTEMPT TO OPEN PATH: " + std::string(TextFormat("data/%s.png", texture_file.c_str()))); #endif Image img = LoadImage(TextFormat("data/%s.png", texture_file.c_str())); // ImageResizeNN(&img, 256, 512); // resize // convert to GPU texture texture = LoadTextureFromImage(img); UnloadImage(img); // create shader outline_shader = LoadShader(0, "data/shaders/outline.fs"); float outlineSize = 2.0f; float outlineColor[4] = {1.0f, 0.0f, 0.0f, 1.0f}; // 0.0-1.0 normalized color float textureSize[2] = { (float)texture.width, (float)texture.height }; // Get shader variable locations int outlineSizeLoc = GetShaderLocation(outline_shader, "outlineSize"); int outlineColorLoc = GetShaderLocation(outline_shader, "outlineColor"); int textureSizeLoc = GetShaderLocation(outline_shader, "textureSize"); // Set shader values (they can be changed later) SetShaderValue(outline_shader, outlineSizeLoc, &outlineSize, SHADER_UNIFORM_FLOAT); SetShaderValue(outline_shader, outlineColorLoc, outlineColor, SHADER_UNIFORM_VEC4); SetShaderValue(outline_shader, textureSizeLoc, textureSize, SHADER_UNIFORM_VEC2); } void PickableObject::draw(void) { // offset by half of image dimensions float draw_x = center.x - ((float)texture.width / 2); float draw_y = center.y - ((float)texture.height / 2); if (selected) { // draw texture with outline BeginShaderMode(outline_shader); DrawTextureSimple(texture, {draw_x, draw_y}, WHITE, 1.0f); EndShaderMode(); } else { // draw without DrawTextureSimple(texture, {draw_x, draw_y}, WHITE, 1.0f); } if (within_aabb) { // draw border around image DrawRectangleLines(draw_x, draw_y, texture.width, texture.height, WHITE); } } void PickableObject::check_collision(Vector2 mouse_coords) { /// first, check for aabb collision if (mouse_coords.x > (center.x - ((float)texture.width / 2)) && mouse_coords.x < (center.x + ((float)texture.width / 2))) { if (mouse_coords.y > (center.y - ((float)texture.height / 2)) && mouse_coords.y < (center.y + ((float)texture.height / 2))) { #ifdef DEBUG debug_out("CLICKED WITHIN AABB OF " + std::string(TextFormat("%s", name.c_str()))); #endif within_aabb = true; /// next check for object selection (pixels) // load image from GPU Image img = LoadImageFromTexture(texture); // get clicked-on pixel coordinates int pixel_x = (int)mouse_coords.x - ((int)center.x - (texture.width / 2)); int pixel_y = (int)mouse_coords.y - ((int)center.y - (texture.height / 2)); #ifdef DEBUG debug_out("CLICKED IMAGE PIXEL COORDS: " + std::to_string(pixel_x) + ", " + std::to_string(pixel_y)); #endif Color pixel = GetImageColor(img, pixel_x, pixel_y); if (pixel.a > 0) // not transparent { #ifdef DEBUG debug_out(std::string(TextFormat("%s", name.c_str())) + " SELECTED"); #endif selected = true; } else { selected = false; } UnloadImage(img); return; } } within_aabb = false; selected = false; } int main(int argc, char *argv[]) { /// ################ SETUP BEGIN ################ SetWindowState(FLAG_MSAA_4X_HINT); SetWindowState(FLAG_VSYNC_HINT); //SetTargetFPS(300); InitWindow(WIN_WIDTH, WIN_HEIGHT, "PIXEL PICKING"); // default to windowed size on creation center_window(); PickableObject gob = {"goblin", "goblin16", {250, 300}}; PickableObject crate = {"crate", "crate", {550, 300}}; /// ################ SETUP END ################ // Enter main game loop while (!WindowShouldClose()) // Until we detect window close button or exit command { /// ################ UPDATE BEGIN ################ if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { Vector2 mouse = GetMousePosition(); gob.check_collision(mouse); crate.check_collision(mouse); } /// ################ UPDATE END ################ /// ################ DRAWING BEGIN ################ BeginDrawing(); ClearBackground(BLACK); // draw our objects gob.draw(); crate.draw(); EndDrawing(); /// ################ DRAWING END ################ } // Cleanup CloseWindow(); // Close window and delete OpenGL context } void center_window(void) { int display = GetCurrentMonitor(); int display_width = GetMonitorWidth(display); int display_height = GetMonitorHeight(display); SetWindowPosition((display_width / 2) - (GetScreenWidth() / 2), (display_height / 2) - (GetScreenHeight() / 2)); } The shader code file, outline.fs, is from the example: #version 330 // Input vertex attributes (from vertex shader) in vec2 fragTexCoord; in vec4 fragColor; // Input uniform values uniform sampler2D texture0; uniform vec4 colDiffuse; uniform vec2 textureSize; uniform float outlineSize; uniform vec4 outlineColor; // Output fragment color out vec4 finalColor; void main() { vec4 texel = texture(texture0, fragTexCoord); // Get texel color vec2 texelScale = vec2(0.0); texelScale.x = outlineSize/textureSize.x; texelScale.y = outlineSize/textureSize.y; // We sample four corner texels, but only for the alpha channel (this is for the outline) vec4 corners = vec4(0.0); corners.x = texture(texture0, fragTexCoord + vec2(texelScale.x, texelScale.y)).a; corners.y = texture(texture0, fragTexCoord + vec2(texelScale.x, -texelScale.y)).a; corners.z = texture(texture0, fragTexCoord + vec2(-texelScale.x, texelScale.y)).a; corners.w = texture(texture0, fragTexCoord + vec2(-texelScale.x, -texelScale.y)).a; float outline = min(dot(corners, vec4(1.0)), 1.0); vec4 color = mix(vec4(0.0), outlineColor, outline); finalColor = mix(color, texel, texel.a); } You're gonna need the attached images to make it run too.
>>565 >The second step is to load the image data from the GPU texture back into main RAM, then query the pixel at the click location to see if it's alpha channel value is higher than 0 - in other words, is not a transparent pixel. Neat. Interesting ideas Anon. GG.
Open file (689.83 KB 707x923 hand7.png)
>>565 Are you sure its a good idea? Pixel perfect selection is only needed when stuff overlaps and you can select the wrong object. In that case you should either avoid that problem, or have priorities, like attacking is always top priority, over opening chests. In fact diablo2 had "-nopickup" option, to disable picking up items, unless you press a button for it. Or have something like regular hitbox system, but 2d, with selection between overlapping objects being made by selecting the object by moving the mouse closer to the center of that object. So when you have 2 objects almost stacked, you can move mouse away from object you dont want. Also you can prebake outlines for static objects, to have 2000 fps instead of 1999. Or change brightness/color slightly. However outlines look way better than changing brightness or color. You dont want to miss when clicking the enemy, so their hitboxes should be a little bigger than they are, and most devs figured out "move only" button to avoid accidently attacking when you want to move. You are not planning to revive primordial quest games, are you? With pixel hunting and all that. And you can have bigger cursor than regular mouse point, so it would look like you hovering over object, even if you hover outside of it. Maybe even slight offset away from the point, but it probably would cause more problems than solve.
>>567 >Are you sure its a good idea? Maybe? I wanted to see how hard it would be to implement first (It's not hard); playtesting comes later. By the design that I'm currently working with, there won't be 'click to attack' like in Diablo and Diablo-likes. Clicking will be used for movement, targetting, and item interaction. It may also be used to command team members like: click on team member to select, click move, click destination, click on other team member, click cast heal, click on self as target for heal, etc. I never liked 'click to attack', or 'click all over the fucking ground trying to pick up the tiny jewel' kind of shit Diablo did. I find those sort of systems painful and tedious. I'm not too worried about overlapping objects either, since items that end up on the ground, as well as enemies, will have collision to prevent them from occupying the exact same space in the world. At which point pixel-perfect selection will let you pick which one you want. Clicking exactly on things is also more natural, I feel, than just clicking in the ballpark of things. It may be a different story though for games that require fast reaction times and high actions-per-minute like RTSs but I'm aiming for a slower, more tactical experience. >Also you can prebake outlines for static objects, to have 2000 fps instead of 1999. I had considered this except that: a single MOB or PC may consist of hundreds of images (animation frames X 8-16 directions), leading to a huge increase in total game size. Sure, that isn't the case for static images but, it's a lot more images to author and keep track of. Before I came across this shader, I was dreading having to write some kind of GIMP plugin or command-line script to generate an outline image for every selectable thing in the game. A shader, too, can be adjusted on the fly to use different line weights and colors. Hell, there is modding potential in it as well, since theoretically anything with a graphic could be clicked on and selected/interacted with, without having to create an outline graphic for it specifically. >You are not planning to revive primordial quest games, are you? With pixel hunting and all that. Why not? RPGs and action games could always stand to have more interaction. I do in fact plan on having small things to click on, like levers and shit, and possibly small items to pick up like keys and coins. I plan to have a zoom feature, which we've seen already, that should make finding and selecting even small objects easy. For instance: you enter a room. It's dark but there's a table with a candle on it. Something glints on the table. You walk up to the table and zoom in. It's the jailer's key ring. You click on it and pick it up. Shit like that. Also, in my little demo today, I used click-to-select, but what I'm really planning on doing is 'hover-to-highlight', to help find things and possibly display contextual information, like a text bubble "door", "key", "portcullis lever", or whatever. So hovering over something will display a border around it, showing you that it's a thing and not just some background decoration, with the color of the outline possibly giving more information (if it's a MOB, container, interactive element, etc.), possibly corresponding to the button used to interact with it (for instance, if the 'attack' icon is red, a MOB may get a red outline, etc.). Clicking on the thing would then interact with it, possibly leaving the outline around it, if it's your current target, or the destination of your 'move to and interact with' action or whatever. We'll see.
Open file (41.21 KB 804x638 lever.mp4)
>>565 Now using an animated texture atlas.
Open file (99.91 KB 804x638 lever_zoom.mp4)
>>569 Now with zoom, in case something is too small to easily see or click on.
>>568 >Also, in my little demo today, I used click-to-select, but what I'm really planning on doing is 'hover-to-highlight', to help find things and possibly display contextual information, like a text bubble "door", "key", "portcullis lever", or whatever. Good thinking Anon. Those kind of little tweaks can make a major difference to the playablity/usability of a game/system. GG.
Open file (6.36 KB 1152x128 lever_anim.png)
>>570 main.cpp and the image used for this: // main.cpp #include "main.h" #include "raylib.h" #include "debug.h" #include <vector> #include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 // Draw a part of a texture (defined by a rectangle) void DrawTextureRecZoom(Texture2D texture, Rectangle source, Vector2 position, Color tint, float scale) { // Check if texture is valid if (texture.id > 0) { float width = (float)source.width * scale; float height = (float)source.height * scale; // x-axis (u?) texture coordinates within 0.0-1.0 uv coordinate space // only works for Nx1 animated textures float text_x_start = source.x / (float)texture.width; float text_x_end = (source.x + source.width) / (float)texture.width; rlSetTexture(texture.id); rlBegin(RL_QUADS); rlColor4ub(tint.r, tint.g, tint.b, tint.a); rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer // Top-left corner for texture and quad rlTexCoord2f(text_x_start, 0.0f); rlVertex2f(position.x, position.y); // Bottom-left corner for texture and quad rlTexCoord2f(text_x_start, 1.0f); rlVertex2f(position.x, position.y + height); // Bottom-right corner for texture and quad rlTexCoord2f(text_x_end, 1.0f); rlVertex2f(position.x + width, position.y + height); // Top-right corner for texture and quad rlTexCoord2f(text_x_end, 0.0f); rlVertex2f(position.x + width, position.y); rlEnd(); rlSetTexture(0); } } class Lever { public: std::string name; Texture2D texture; Shader outline_shader; Vector2 center; // location of the center of the texture on the screen std::vector<float> zoom_levels = {0.0625f, 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f, 16.0f}; int zoom_index = 4; // current zoom level float zoom = zoom_levels[zoom_index]; bool within_aabb = false; bool selected = false; // animation int total_frames = 9; int current_frame = 0; int frame_size_x = 128; int frame_size_y = 128; bool moving = false; float elapsed = 0.0f; float frame_step = 0.12f; // seconds per animation frame // movement directions bool pos_right = true; // switch is in the 'right' position bool pos_left = false; // switch is in the 'left' position // constructor Lever(std::string name, std::string texture_file, Vector2 center); // class methods void draw(void); void update(void); void check_collision(Vector2 mouse_coords); }; Lever::Lever(std::string name, std::string texture_file, Vector2 center) { // set name this->name = name; // set position this->center = center; // load image #ifdef DEBUG debug_out("ATTEMPT TO OPEN PATH: " + std::string(TextFormat("data/%s.png", texture_file.c_str()))); #endif Image img = LoadImage(TextFormat("data/%s.png", texture_file.c_str())); // ImageResizeNN(&img, 256, 512); // resize // convert to GPU texture texture = LoadTextureFromImage(img); UnloadImage(img); // create shader outline_shader = LoadShader(0, "data/shaders/outline.fs"); ///float outlineSize = 2.0f; float outlineSize = 1.0f; // seems to only work with integer values, makes sense since it's pixel width float outlineColor[4] = {1.0f, 0.0f, 0.0f, 1.0f}; // 0.0-1.0 normalized color float textureSize[2] = { (float)texture.width, (float)texture.height }; // Get shader variable locations int outlineSizeLoc = GetShaderLocation(outline_shader, "outlineSize"); int outlineColorLoc = GetShaderLocation(outline_shader, "outlineColor"); int textureSizeLoc = GetShaderLocation(outline_shader, "textureSize"); // Set shader values (they can be changed later) SetShaderValue(outline_shader, outlineSizeLoc, &outlineSize, SHADER_UNIFORM_FLOAT); SetShaderValue(outline_shader, outlineColorLoc, outlineColor, SHADER_UNIFORM_VEC4); SetShaderValue(outline_shader, textureSizeLoc, textureSize, SHADER_UNIFORM_VEC2); } void Lever::draw(void) { zoom = zoom_levels[zoom_index]; // offset by half of image dimensions float draw_x = center.x - ((float)frame_size_x / 2) * zoom; float draw_y = center.y - ((float)frame_size_y / 2) * zoom; Rectangle frameRec = {(float)(frame_size_x * current_frame), 0.0f, (float)frame_size_x, (float)frame_size_y}; if (selected) { // draw texture with outline BeginShaderMode(outline_shader); ///DrawTextureSimple(texture, {draw_x, draw_y}, WHITE, 1.0f); DrawTextureRecZoom(texture, frameRec, {draw_x, draw_y}, WHITE, zoom); EndShaderMode(); } else { // draw without ///DrawTextureSimple(texture, {draw_x, draw_y}, WHITE, 1.0f); DrawTextureRecZoom(texture, frameRec, {draw_x, draw_y}, WHITE, zoom); } if (within_aabb) { // draw border around image DrawRectangleLines((int)draw_x, (int)draw_y, (int)((float)frame_size_x * zoom), (int)((float)frame_size_y * zoom), WHITE); } } void Lever::check_collision(Vector2 mouse_coords) { zoom = zoom_levels[zoom_index]; /// first, check for aabb collision if (mouse_coords.x > (center.x - ((float)frame_size_x / 2) * zoom) && mouse_coords.x < (center.x + ((float)frame_size_x / 2) * zoom)) { if (mouse_coords.y > (center.y - ((float)frame_size_y / 2) * zoom) && mouse_coords.y < (center.y + ((float)frame_size_y / 2) * zoom)) { #ifdef DEBUG debug_out("CLICKED WITHIN AABB OF " + std::string(TextFormat("%s", name.c_str()))); #endif within_aabb = true; /// next check for object selection (pixels) // load image from GPU Image img = LoadImageFromTexture(texture); // get clicked-on pixel coordinates int pixel_x = (int)mouse_coords.x - ((int)center.x - (int)(((float)frame_size_x / 2) * zoom)); pixel_x += (int)((float)frame_size_x * (float)current_frame * zoom); // slide to current animation frame pixel_x = (int)((float)pixel_x / zoom); int pixel_y = (int)mouse_coords.y - ((int)center.y - (int)(((float)texture.height / 2) * zoom)); pixel_y = (int)((float)pixel_y / zoom); #ifdef DEBUG debug_out("CLICKED IMAGE PIXEL COORDS: " + std::to_string(pixel_x) + ", " + std::to_string(pixel_y)); #endif Color pixel = GetImageColor(img, pixel_x, pixel_y); if (pixel.a > 0) // not transparent { #ifdef DEBUG debug_out(std::string(TextFormat("%s", name.c_str())) + " SELECTED"); #endif selected = true; moving = true; } else { selected = false; } UnloadImage(img); return; } } within_aabb = false; selected = false; } void Lever::update(void) { if (moving) { // see if it's time to progress to next animation frame elapsed += GetFrameTime(); if (elapsed > frame_step) { elapsed -= frame_step; if (pos_right) // moving from right to left { current_frame++; if (current_frame > (total_frames - 1)) { current_frame = (total_frames - 1); moving = false; pos_right = false; pos_left = true; selected = false; } } else if (pos_left) // moving from left to right { current_frame--; if (current_frame < 0) { current_frame = 0; moving = false; pos_right = true; pos_left = false; selected = false; } } } } /// handle mouse wheel zoom float mw = GetMouseWheelMove(); //if (abs(mw) > 0) if (mw != 0.0f) { #ifdef DEBUG debug_out("MOUSE WHEEL: " + std::to_string(mw)); #endif if (mw > 0) { // zoom in zoom_index++; #ifdef DEBUG debug_out("ZOOMING IN"); #endif } else { zoom_index--; #ifdef DEBUG debug_out("ZOOMING OUT"); #endif } if (zoom_index < 0) { zoom_index = 0; } else if (zoom_index == (int)zoom_levels.size()) { zoom_index = ((int)zoom_levels.size() - 1); } #ifdef DEBUG debug_out("ZOOM LEVEL: " + std::to_string(zoom_levels[zoom_index]) + "X"); #endif } } int main(int argc, char *argv[]) { /// ################ SETUP BEGIN ################ SetWindowState(FLAG_MSAA_4X_HINT); SetWindowState(FLAG_VSYNC_HINT); //SetTargetFPS(300); InitWindow(WIN_WIDTH, WIN_HEIGHT, "PIXEL PICKING - ANIMATED"); // default to windowed size on creation center_window(); Lever lever = {"lever", "lever_anim", {400, 300}}; /// ################ SETUP END ################ // Enter main game loop while (!WindowShouldClose()) // Until we detect window close button or exit command { /// ################ UPDATE BEGIN ################ if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { Vector2 mouse = GetMousePosition(); lever.check_collision(mouse); } lever.update(); /// ################ UPDATE END ################ /// ################ DRAWING BEGIN ################ BeginDrawing(); ClearBackground(BLACK); // draw our objects lever.draw(); EndDrawing(); /// ################ DRAWING END ################ } // Cleanup CloseWindow(); // Close window and delete OpenGL context } void center_window(void) { int display = GetCurrentMonitor(); int display_width = GetMonitorWidth(display); int display_height = GetMonitorHeight(display); SetWindowPosition((display_width / 2) - (GetScreenWidth() / 2), (display_height / 2) - (GetScreenHeight() / 2)); }
Open file (39.72 KB 1000x1000 aaa reference tiles2.png)
Open file (122.19 KB 836x557 ClipboardImage.png)
Open file (776.96 KB 409x500 1468398307939.gif)
I wonder if I should chop tiles like picrelated (red tiles chopped into blue sub tiles), or chop them into smaller diamonds, or stop fucking around and keep tiles like they are right now(maybe a little bit smaller. I think, chopping them into blue rectangles should be a good idea, since it works for wall tiles, and I can avoid rendering ground tiles which hide behind the wall (optimizing for 2k+ fps, I really should stop). But it might fuck up shading a little. But if a I wanted perfect shading, I would use proper shaders in openGL. And I dont need to render/store walls and ground the same way. In fact I dont need to store and render any kind of tiles the same way. And I can render roads on top of other ground tiles, instead of making merged road+ ground tiles, which would save time and effort for me, since it is quite annoying thing to do. I dont even need diamond shape, other than it being quite useful, in case I switch to more efficient way to store/render tiles, or just sell tiles. And in general, its useful for dimetric projection I am using. And I plan to make tools to chop tiles into proper shape anyway, and I am likely will have tiles which are bigger than intended and overlap other tiles, like grass, which should look good. In fact its applicable to all tiles, especially walls/objects. But without diamond subtiles, tile grid and data grid will be at 45 degree angle, despite both being 2d matrix... I think, I will chop sub-tiles like in reference picrelated, at least until I make proper metadata+tile packer tool. Either 4x4 subtile grid for 64x32 subtiles or 8x4 for 32x32 subtiles.
Open file (240.66 KB 1024x1024 savespace.png)
Open file (359.48 KB 1284x758 attempt.png)
Open file (367.35 KB 1284x758 wouldbenice.png)
>>573 I'd just keep what you have and move on. It's fun to think about how to optimize for file size or minimum overdraw or whatever, and I made a mock-up showing a couple of different ways, but it's just not worth it at this point. Like I said before, saving tiles as PNG, either separately or packed into an atlas (I do both), and rendering them as-is, should be fine for our purposes. The PNG file format compresses nicely, especially when you have large contiguous areas. A "diamond" isometric floor tile may be 50% blank, "wasted" space, but I guarantee you its file size is not double that of one with only the visible pixels. The overdraw issue as well, is likely a non-issue, since it seems OpenGL, and probably Directx and Vulkan as well, are optimized to very quickly deal with transparent pixels, given their prevalence in 2D and 3D games. >I dont even need diamond shape, other than it being quite useful, ... You actually really do, because like you said, it's quite useful. The diamond marks the bounds of an object in 3D space, whether a tile is 1m x 1m, or whatever your scale is. It allows you to properly depth sort your static and dynamic objects quickly and easily (First, compare x and y grid coords. If they're on the same grid coordinate, compare world space x, y). It should start to come together better when we get more walls/buildings/objects in place. >tile grid and data grid will be at 45 degree angle, despite both being 2d matrix... This will absolutely fuck you. I'd like to think that, given it's long history and sheer number of games using it, that the tried and true isometric "2.5D" projection of a 3D world to a 2D screen cannot be (easily) improved upon. >subtiles I really wouldn't. Honestly, it's probably best if we moved on to gameplay and set some of the graphical stuff aside for now. I'm confident that the issues will get worked out in time. Really though, I wouldn't sweat the graphical "optimization". It's just one of many possible representations of what should be a plausible 3D space, and it's that game world and gameplay that, at least myself, need to focus on now.
>>574 Maybe I should explain the first pic. It's two different "optimizations" that one could do. The top bit is if you took your regular old rectangular texture with "diamond" visible isometric part, and UV mapped the inner diamond to a diamond-shaped quad and rendered that. This renders only the visible part, clipping the transparent bits. This eliminates the so-called overdraw problem. Still 1 texture, 1 quad. I may benchmark this compared to rendering the whole texture sometime if I get bored. The middle bit is another method: using a square texture with no transparent parts mapped to a diamond-shaped quad, such that it is rotated -45 degrees and squished to half height. Eliminates the so-called wasted space/excess file size problem but comes with major drawbacks - it's no longer "pixel perfect" 1 to 1 mapping with what is rendered to screen. You will need to use bilinear texture filtering for it to not look like total ass. Also, if you're using power-of-two texture sizes, 128x128 will be larger than a 128x64 isometric tile (and possibly look worse) and 64x64 will be smaller than a 128x64, but need to be upscaled and stretched (definitely look worse). Also, both of these "solutions" only work for perfectly flat (that is, no depth, level transitions, grass sticking up, anything) diamond "floor" tiles. As you can see, both these methods come with serious drawbacks and limitations and you will probably not see these techniques employed anymore.
>>575 >I may benchmark this compared to rendering the whole texture sometime if I get bored. From what I read, rendering a single triangle, and rendering 2k triangles is exactly the same, because of how gpu works. In fact, I suspect that textured 3d grid without shaders will render faster than my "render one 32x32 tile at a time" approach, because rendering 3d scene is done as a single cpu>gpu action, while cpu calls for rendering of each tile. But its only 4k ground tiles and for zooming out, I will use a trick, in which I render same ground tiles (but scale down data grid), but only characters will be squished. So regular 32x32 tile will look exactly the same, but instead of being 2m*2m it will represent 4m*4m. 2 tile wide road will become 1tile wide road. >using 3d Kind of pointless to me, since I use true 2d, because using 3d is much more work. If I use 3d ground(which is the most 2d thing I have) why not use 3d models for walls? And why not make ground stuff to be actual 3d modeled? And if 3d models for walls, why not use them for characters? It will be better and solve some problems, especially with animations, but its a lot of work. I suspect I will have to move to 3d, but not today. The main goals are to have an engine, which is easy to work with, and easy to make content for it, and for it to look good enough.
Open file (23.98 KB 200x200 hairy.png)
Open file (118.05 KB 1494x881 hairytiled.png)
>>577 >From what I read, rendering a single triangle, and rendering 2k triangles is exactly the same, because of how gpu works. This is true. I believe it's called batch rendering. There is a Raylib demo that demonstrates this, though it's all handled under-the-hood: ht tps://www.raylib.com/examples/textures/loader.html?name=textures_bunnymark >In fact, I suspect that textured 3d grid without shaders will render faster than my "render one 32x32 tile at a time" approach, Raylib is, and I suspect your libraries are as well, in fact, all 3D and running on the GPU (the shape-drawing functions are software, IIRC). You are using SDL right? Is there no SDLDrawQuad(Texture texture, Quad quad, Color ...) etc. function? What are you using to draw images to the screen now? >using 3d >Kind of pointless to me, since I use true 2d, because using 3d is much more work. I think you misunderstood me. Let me try again. I don't think you're retarded; I just haven't been clear enough. The worlds you and I are creating, as they exist in our minds and in our design notes, are fundamentally 3-dimensional. Buildings, fences, walls, objects, whatever, have height. Human males might be 6ft on average, whereas a goblin might be 4ft. They have height. This is all independent of how we try to present these worlds to the player. You may have bats or birds flying around. They get drawn with a Y-axis offset to show them as being above their X, Y position. In our case, we have chosen the "2.5D", "isometric" perspective to present this 3D world to the player. Why? Because the isometric perspective allows the viewer to see the front, left, and top of an object (or right, back, top, whatever, depending on its orientation). These 3 views, similar to what you might see in a 3D editor, are enough to accurately communicate something's 3-dimensional form. If something looks like a circle from those 3 angles, you know it's a sphere. If something looks like a square from those 3 angles, you know it's a cube. From a gameplay point of view as well, it allows the player to move freely in the X, Y plane, while also allowing a look at things' vertical dimension (a door in a wall, the face of an NPC, etc.) that a strictly top-down view does not. Of course, we aren't using 3D engines with 3D models to achieve this effect. I'm using pre-rendered 2D sprites just like you are, and for the same reasons that you are - it is less work. I mean, look at this asshole. My poor fucking goblin, stolen from a stock photo site and cruelly reduced to only 16 colors, is shown here jumping up and down jankily. Everything in this scene is a 2D texture, drawn on a 2D plane. The underlying asset, the Goblin(), really has a z coordinate though. I use that to determine the Y-offset to draw him at, and also the position of his shadow. In the underlying simulation, he is well and truly moving up and down. For the 2-dimensional on-screen presentation of that, I have to use some tricks. I do hope you get what I'm saying. >If I use 3d ground(which is the most 2d thing I have) You can render a lumpy, bumpy, rocky, grassy 3D terrain to a 2D texture and draw that. In fact, you're already doing it. See my edit of one of your ground tiles that exaggerates the z-axis element. You can have a 3-dimensional element to it, in this case, the rocks and grass, while still having it tile nicely (if drawn properly back to front). That's what I meant. That's the beauty of pre-rendered 3D. There are always drawbacks though - make your ground tiles too "3D" and things will look weird moving over them. >why not use 3d models for walls? And why not make ground stuff to be actual 3d modeled? And if 3d models for walls, why not use them for characters? It will be better and solve some problems, especially with animations, but its a lot of work. Well, yeah. That must have been the question Blizzard asked themselves when they started work on Diablo 3, because that game's all realtime 3D. There are strengths and weaknesses to each approach. I considered them and went with 2D. Blizzard considered them and went with 3D for D3 and D4. Realtime 3D allows for more animations, more character skins and visible items, realtime lights and shadows, and so on and and so forth. >I suspect I will have to move to 3d, but not today. You just might. As might I. As long as we keep the world being simulated separate from it's on-screen representation, there shouldn't really be any problem switching over. >The main goals are to have an engine, which is easy to work with, and easy to make content for it, and for it to look good enough. You and me both.
>>581 >Well, yeah. That must have been the question Blizzard asked themselves when they started work on Diablo 3, because that game's all realtime 3D If I was a big studio, I would not bother with 2d. However they do optimize a lot of things, like how trees are basically a couple of 2d planes. And with normal maps, a lot of things could be 2d with shaders. >sdl draw It only can store and render textures and primitives. As far as I know, everything works like on analogue monitor, Textures are 1d array of pixels (when you reach X res of texture, it moves to the next line), which are rendered to buffer/texture. (or surface, which is like a texture, but stored in ram, not gpu). >I think you misunderstood me. Let me try again Yeah, I will have few layers, and I considered rendering some grass as part of object layer, not ground, like picrelated. Or have tiles which are "bleed out" of their boundaries. like in your 2 and 3 pictures. But I dont think I will, because I cant think of a situation I need to, except for grass. With stones I can render them in the middle of the tiles, or in object layer. And other than random stones and grass, what else belongs in the ground layer but has height? And stuff like levelchange in >>561 will be walls instead. Also, a little trick I read somewhere. Make walls and other similar tiles a little bit darker at the bottom, it helps perspective. You also can make ground tiles a little bit brighter. But second one is probably less relevant if we render everything in blender, in which case ground likely will be brighter anyway. >separate rendering and game data It cant be done completely, since everything affects each other. I decided that player character will be 200 pixels tall(or 180), which dictates how tall objects are, which dictates level of details I need, and size of tiles. And it affects how I should draw objects, like in wall ref picrelated. I will need walls to be around 6m tall, when modeling, so they will cover empty ground tiles when Z levels change, and 2.825 meters tall in some other situations. I do generate everything procedurally, but even so, its hard to change how things look, in different resolution/scale. >>558 in this I just scaled down grass texture, which turns it into green noise. I do plan to render stuff at double or triple resolution, so stuff would look better(than simply rendering at final tile resolution), but I need to know final resolution of sprite, to make details at appropriate scale, so they dont turn into mush. Its not the end of the world, to redo stuff (I have like 20 versions of differently rendered grass) but still.
I've wanted to start on an ARPG for a long time, but the amount of art needed seems overwhelming. God speed anons working on them.
Open file (1.51 MB 1920x1080 chairs.png)
Open file (607.59 KB 1000x1000 well.png)
Open file (950.04 KB 1000x1000 flamewall.png)
Open file (651.39 KB 1000x446 walls rocky good.png)
Open file (2.69 MB 1920x1080 tree bette.png)
>>857 Its not that many assets, but procrastination is a cruel mistress. Making them work together is a bigger problem, at least for me. I am not a professional, but I can make good looking things. But I cant predict how things will turn out, and whenever they would look like what I envisioned. For example wall ended up good looking, but I wanted it to look the same as the well rocks. Flamewall looks stylish, but it doesnt fit with realistic looking assets. So half of assets are fucking useless to me. Originally I wanted a mech game, but other than some okay looking mech parts, and a single house, and some roads I cant really make good looking modern/futuristic assets. Also level of detail is difficult to figure out for a game, because small details will disappear when its too small. But making assets is not that difficult. Except for difficult parts... And its not like you making everything "by hand" almost everything is in bigger part generated by blender, than made by you, you just determine parameters for generation.
>>857 You just have to approach it with a plan. Come up with a game that deserves to be made - something original, an evolution of/improvement on existing titles, or otherwise just demonstrably fun. Then, referencing design guides like this one ht tps://book.leveldesignbook.com/process/overview plan out a vertical slice of the game, and just get started on it. Design, experiment, playtest, iterate. When you've got something playable (even if 100% gray-boxed with placeholder assets) and its FUN, artists will come to you. You've already found a thread where at least a couple other people will know what you're talking about and that you can bounce ideas off of.
>>489 >The guy who made Flare ht tps://flarerpg.org/ sums it up better than I can here Which brings us to the obvious question: if you want Diablo-like, why not start with, uh, FLARE engine?
>>895 >if you want Diablo-like, why not start with, uh, FLARE engine? Maybe. How did that work for you Anon? Was it easy to use?
>>895 Because 1. it's GPL, not even LGPL, and as an engine tends to be the core of a game project (touches everything), it requires you to release the full source of your game. 2. As it's feature set is pretty fucking basic (no lighting, no wall transparency handling for instance, the first point becomes that much more onerous. FLARE dev is a cuck for using a jewish license and his contributions to game dev (his little write-ups on how ARPG stuff works and the assets he released on OpenGameArt which might be good for reference at least) likely don't make up for the fact that his code is forever poisoned and removed from wider use (no really, don't even look at GPL code; you may be able to "borrow" some for a binary-only release now and then, but a source release under an actually free license like MIT, ZLIB, CC opens you up to claims of infringement). See John Carmack's tweets about his regrets on releasing ID software code as GPL. Don't agree? Show me all the games made with FLARE (released 2013 by the way) then. My research turned up Ghostlore http://ghostlore.sg/ as the only released title, with this amusing note on the FLARE engine page: >The developers switched away from Flare for the final game. However, the old web demo uses Flare as its engine. So yeah, it's shit.
>>897 Not that anon but regarding GPL and games I think it's fair to say that it was extremely beneficial for Id despite Carmack's stance on it now. Would the original Doom or Quake continue to be a revenue stream if source port developers could keep their changes to themselves and players were basically left with increasingly limited DOS-era executables? A look at certain modding scenes for example shows that people are willing to try and keep their work to themselves even despite the highly derivative (of the source material) nature of most mods. Without the community-developed innovations made over the decades modern Id wouldn't exist as it does, it's because of continued interest in their old games through impressive mods and enhanced ports that their most recent successes came to be (the connection between Brutal Doom and Doom 2016 is pretty apparent I think). In that way it's not purely the technology that defines your game but the quality of the experience it powers, distributing your code but keeping your data proprietary is probably better than full proprietary for the long term health of your game going by the effect it had on Id. That's not even getting into the future preservation benefits.
>>897 /thread.
>>899 Carmack's stance on it changed precisely because no benefit was observed between then and when he tweeted. Carmack's observation, not shower-thought mind you, was that releasing old engines as GPL did not bring out the desired goal, that is, advancing 3D games and the FPS genre, because anyone with any skin in the game either licensed the current tech from Id or developed their own (closed source) because the GPL is a big legal no beuno. Instead he got an autistic and increasingly degenerate "mod community" banging on 30 year old tech because that's all that's "free". >Would the original Doom or Quake continue to be a revenue stream They are? I'm pretty sure there was a Doom 2, Quake 2, Q3A, Doom 3, Prey, Doom 4, Quake Wars, Quake 4, DOOM 2016, etc. somewhere in there as well. Are you saying Id is making money off of Doom and Quake today? Id makes their revenue off of first-party titles and tech licensing. I am not aware of any kind of paid-mods revenue-sharing or anything like that. >if source port developers could keep their changes to themselves and players were basically left with increasingly limited DOS-era executables? Strawman. Presumes source port developers would keep their changes to themselves (which is fine too, as it actually introduces diversity and competition) and I don't understand the second part, you would have to play the original versions instead because the superior source ports didn't all release their code? >Without the community-developed innovations made over the decades modern Id wouldn't exist as it does Gotta be kidding me. Remember when they all went out and bought million-dollar supercars and had regular company "track days" after Doom's release? Id only released source for engines that were over a generation behind their current ones (and each engine was a quantom leap beyond the previous), and it's a fact that not a single line of community code made it back into an Id software product (you can go closed -> GPL but not the other way around). >it's because of continued interest in their old games through impressive mods and enhanced ports >mods This is the real answer for Id's success, and it happened before any source release. Id titles were successful because they were ground-breaking in their mechanics, bleeding-edge technically, and edgy in their content, not because you can play it on linux with normal-mapping or shadows 10 years later. >distributing your code but keeping your data proprietary is probably better than full proprietary for the long term health of your game going by the effect it had on Id. And here's the GPL apologist finisher: if you're not GPL you're proprietary. Never gets old. Here's my argument, and for what its worth Carmack agrees: releasing as GPL is a death sentence for code - no one will do anything of worth with it. It is also not "free"; it places restrictions on its use and obligations on the user. Releasing code as MIT or other free license, will result in mass adoption, as it can be used by anyone, for anything, even if you don't like what they do with it, like make changes and not tell you how they did it, or *gasp* charge money for them. Carmack didn't want to DOOM, etc. source proprietary - he wanted to release his engine code under a more open license, but Id's legal department would not let him - they were afraid it would hurt their bottom line, that other studios would use their old code as a base to "get up to speed" and be competitive with Id at some point. Thus, code of incredible value was essentially put in a museum. MIT, BSD, Apache, ZLib, etc. are the de-facto games industry standard free licenses for a reason and you absolutely cannot go wrong with them. Use them, disregard those who don't. Arguments about the linux kernel or cryptographic software are not applicable here. Honestly, the source-port scene is fucking terrible. 99% of source ports out there are GPL (Opengothic being one of the only exceptions I know of). Is this a legal thing? Cover your ass thing? I don't understand. Searching for "games made with (source port)" results in exactly 0 games every time, without fail. If you really wanted to contribute something, you'd make a clean-room copy of some legendary game like 007 Goldeneye or Halo 1 or Ocarina of Time or some shit, release it MIT, then release the fucking asset pipeline too (converters from Blender to your mesh/animation format, animation rigs, shaders, level/map formats and their editors, scripts, file archivers/packers/compressors, etc.) - this is what everyone seems to conveniently forget. Then you'd actually have something more than a line on your gamedev resume and 5K followers on discord. Sage for off-topic. >>900 lol don't kill my thread. That said, license drama (of which I am the most guilty) should be its own thread, something like "gamedev legal/ethics/financial".
>>902 >lol don't kill my thread. Heh my mistake Anon. Clearly you have more to contribute, please proceed. :)
>>903 Was joke, comrade :^). And nah, I'll stop shitting up the thread. Maybe somewhere else, some other time.
>>902 >license drama (of which I am the most guilty) should be its own thread, something like "gamedev legal/ethics/financial" If we do end up with one, I am going to use it to yell about shitbirds who pretend to offer assets under Creative Commons licenses but then add restrictive "gotcha" supplemental terms that Creative Commons specifically disallow under their trademark policy. >you can have this under CC-BY (unless your game's sales exceed $X) hurr durr now we come up in CC license search results make sure you read the Free* license terms! :) IF YOU USE SUPPLEMENTAL TERMS TO IMPOSE ADDITIONAL RESTRICTIONS OF ANY KIND IT'S NOT CC-BY ANYMORE IS IT, CALL IT SOMETHING ELSE LIKE THE YOU CAN USE THIS BUT I WANT A CUT LICENSE FUCK I KEEP SEEING THIS SHIT EVERYWHERE I AM SO MAD
>>902 >GPL did not bring out the desired goal, that is, advancing 3D games and the FPS genre Well that's not so easy to measure, since the lessons developers have learned from Id's codebases aren't necessarily so explicitly stated, as a learning resource the Doom 1-3 & Quake 1-3 sources have been invaluable to many game devs. >"mod community" banging on 30 year old tech because that's all that's "free" Seems more like a problem with the culture of game development, getting the sources for 20+ year old games if the planets align shouldn't be the norm but it is. Also the things people accomplish with those very old engines are very often more experimental and innovative than what you generally get from the modern industry, you have to at least appreciate the dedication involved. >I'm pretty sure there was a Doom 2, Quake 2, Q3A... Well yeah, but Doom 1+2 and Quake 1 have the most popularity in large part due to what the community does with them, which gets spread around various places and gets people outside those communities interested in playing these new mods and total conversions. >Are you saying Id is making money off of Doom and Quake today? Yep, for Id it's a very low maintenance deal: By providing the games on Steam and other stores (generally just the original DOS versions wrapped with DOSBox) players buy the games, then take the data files and play them with source ports and mods developed completely by outside devs, Id doesn't need to get involved at all and can just let the money come in. >Presumes source port developers would keep their changes to themselves I base that on observations of other scenes where sharing your work isn't a requirement. >which is fine too, as it actually introduces diversity and competition Personally I don't think so, in those scenarios you often get certain projects having lots of control over the community, where what they say goes and people have much less power to mitigate the damage in the event of a lead dev with bad intentions. >you would have to play the original versions instead because the superior source ports didn't all release their code? That's fair, I didn't think that completely through. My concern was more about the issue of continuous maintenance when developers inevitably stop working on their port and that project is closed source, such that eventually you end up needing a jenga tower of fixes for fixes of ports as operating systems and hardware move on. >Gotta be kidding me... My point was about Id software since 2016, not during their '90s successes. The original Doom 4 was a 50 shades of brown modern shooter, which they scrapped and instead took great inspiration from Brutal Doom and the Doom community in general. >and it happened before any source release Id game modding pre-source code was actually far more limited than what people create now with modern ports, Doom for example only had Dehacked which is very primitive compared to the power of technologies like Zscript. >not because you can play it on linux with normal-mapping or shadows 10 years later The continued development enabled their games to remain much more relevant than they would be, it's the long term of a game that source releases benefit most. >if you're not GPL you're proprietary My impression was part of your issue with GPL was that it could contaminate proprietary projects through code observation (GPL enforcement mainly seems to affect more flagrant violations like outright code copying from what I've seen), but maybe that's a misunderstanding. >releasing as GPL is a death sentence for code - no one will do anything of worth with it Well Linux is the obvious counter point to that, Torvalds doesn't even care about FOSS but he got lots of code contributions out of it which is what he wanted. >It is also not "free"; it places restrictions on its use and obligations on the user That's about maintaining freedom, otherwise you can end up in a situation like MINIX & Intel where big players can take things for themselves and try to prevent others from having the freedom they did (Intel ME). >or *gasp* charge money for them You can sell GPL software, that is one of the core features of Stallman's idea of software freedom after all. For example, Krita is GPL3 but sold on storefronts like Steam. >code of incredible value was essentially put in a museum New games are being made with these engines and sold, they're hardly untouchable artifacts that you can only glance at. >MIT, BSD, Apache, ZLib, etc. are the de-facto games industry standard free licenses for a reason Yes because the software business in general likes having absolute control (especially over users) and keeping their code to themselves where possible, that doesn't mean that long term it's the best strategy: The money spent on remasters for instance could be greatly reduced with community engines, not only increasing revenue for re-releases but making it much easier to keep a game somewhat relevant without having to invest nearly as much in upkeep of that game. >Arguments about the linux kernel or cryptographic software are not applicable here They're good examples of why GPL works so I don't see how that isn't applicable. >Is this a legal thing? Cover your ass thing? I don't understand Maybe devs just believe it's a good idea, preservation is a major aspect of source port development and copyleft helps keep updated code available over a longer time period. >"games made with (source port)" results in exactly 0 games For GZDoom there's quite a number (including games like Hedon, Vomitoreum etc): https://zdoom.org/wiki/Standalone_games On Quake (Darkplaces) there's Wrath: Aeon Of Ruin, Xonotic, Nexuiz Classic and perhaps others I can't think of. >you'd make a clean-room copy of some legendary game like 007 Goldeneye or Halo 1 or Ocarina of Time or some shit 007 is in the works, OOT is already done. >Then you'd actually have something more than a line on your gamedev resume As a counter point I'd mention Kaiser from the Doom community, he did lots of stuff for Doom and now works at Nightdive as a programmer, his Kex engine is what powers most of their remasters.
>>896 I did not mess with it a whole lot, mostly played through the default game, tried mods, modded a little. Demo game: worked nicely. Simple ruleset and simple resource modding (adding a new ability, etc): was indeed trivial. Variety between styles of the existing resource sets: good. From Diablo-like (Flare-game) to jRPG style (Polymorphable). https://flarerpg.org/mods/ The possible downside: Rummaging through assets of Flare-game shows that for all this fancy equipment in hands to look so nice the sprites require not only a set of standard angles, but fuckery with split layers, which does not look trivial. Not unexpected (FreedroidRPG data/graphics/tux_motion_parts/ is not for the faint of heart either, that’s just how sprite based engines do it). Probably… maybe… hopefully there’s something in documentation on how to do this efficiently.

Report/Delete/Moderation Forms
Delete
Report

no cookies?