Python has some nice tools to enable to load and save objects, in particular to a json file.
with open('levels.json', 'r') as file: levels = json.loads(file.read())
and we have all our levels loaded. In this case its a grid of cells, each cell potentially holding 3 layers. The bottom layer is for walkable tiles and crate targets - this puzzle game has us pushing crates onto target squares. As well as crates there are immovable walls, typically on the middle layer, which is also holding markers for the crate start positions.
I'll go through the controls of the level editor before presenting the code
the 'l' (lower case L !) key takes you to the next level or creates a new one, holding shift and you go backwards a level.
[ & ] allow you to change the current tile to place
Space places the current tile on layer 0, "1" places the current tile on the middle layer "2" places the current tile on the top layer
Holding down the "t" key shows you the whole sprite sheet allowing mouse selection of the current tile
finally pressing "s" saves all the levels...
a fair bit of functionality, and in future the game will rely on certain tile values in the level to place items when the level start.
tile 55 denotes the player start position.
tiles 1-10 denote crates start position
on tiles 76 an 87 are walkable 87 being the crate targets
here is the somewhat rudimentary level editor to play with
#!/usr/bin/env python3 import json from raylib.static import rl, ffi from raylib.colors import * from enum import Enum # these are the different movement states for the player class Moving(Enum): NOT = 0 UP = 1 DOWN = 2 LEFT = 3 RIGHT = 4 def updatePlayer(laFr, lisMoving, lmoveCount): fi = 55 # if moving update the move counter if lisMoving != Moving.NOT: fi += laFr lmoveCount -= 1 if lmoveCount == 0: lisMoving = Moving.NOT # if not moving check for movement key presses if lisMoving == Moving.NOT: if rl.IsKeyDown(rl.KEY_UP): lisMoving = Moving.UP lmoveCount = 32 if rl.IsKeyDown(rl.KEY_DOWN): lisMoving = Moving.DOWN lmoveCount = 32 if rl.IsKeyDown(rl.KEY_RIGHT): lisMoving = Moving.RIGHT lmoveCount = 32 if rl.IsKeyDown(rl.KEY_LEFT): lisMoving = Moving.LEFT lmoveCount = 32 if lisMoving == Moving.DOWN: playerPos.y += 2 if lisMoving == Moving.UP: playerPos.y -= 2 fi += 3 if lisMoving == Moving.RIGHT: playerPos.x += 2 fi += 22 if lisMoving == Moving.LEFT: playerPos.x -= 2 fi += 25 return fi, lisMoving, lmoveCount # takes a texture tile from an image sprite sheet def getTxFromSheet(source, ix:int , iy:int): ii = rl.ImageFromImage(source, [ix*64,iy*64,64,64]) tx = rl.LoadTextureFromImage(ii) rl.UnloadImage(ii) return tx #// Initialization #//-------------------------------------------------------------------------------------- screenWidth = 1280; screenHeight = 720; rl.SetConfigFlags(rl.FLAG_MSAA_4X_HINT| rl.FLAG_WINDOW_RESIZABLE) # Enable Multi Sampling Anti Aliasing 4x (if available) rl.InitWindow(screenWidth, screenHeight, b"step 4 - level editor!") sheet = rl.LoadImage(b"data/sokoban_tilesheet.png") # load in all the tiles... tiles = [] for y in range(0,8): for x in range(0,11): tiles.append(getTxFromSheet(sheet,x,y)) rl.UnloadImage(sheet) with open('levels.json', 'r') as file: levels = json.loads(file.read()) frame = 0 aFr = 0 isMoving = Moving.NOT moveCount = 0 playerPos = ffi.new("struct Vector2 *",[ screenWidth/2, screenHeight/2]) level=0 placeTile=0 rl.SetTargetFPS(60) while not rl.WindowShouldClose(): #// Detect window close button or ESC key #// Update #//---------------------------------------------------------------------------------- frame+=1 if not(frame % 6): # every 6th frame aFr+=1 # increment player frame if aFr == 3: # does it need to wrap (return to zero) aFr=0 # update the player tells us the animation frame for the player # movement status and the count through the move playerFrame, isMoving, moveCount = updatePlayer(aFr, isMoving, moveCount) #//---------------------------------------------------------------------------------- #// Draw #//---------------------------------------------------------------------------------- rl.BeginDrawing() rl.ClearBackground(GRAY) if rl.IsKeyPressed(rl.KEY_L): if rl.IsKeyDown(rl.KEY_LEFT_SHIFT) or rl.IsKeyDown(rl.KEY_RIGHT_SHIFT): level -= 1 if level == -1: level = 0 else: level += 1 if len(levels) != level+1: l=([ [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]], [[0],[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]] ]) levels.append(l) # draw the level if not viewing tiles if not rl.IsKeyDown(rl.KEY_T): for y in range(0,11): for x in range(0,20): for i in range(0,3): if i < len(levels[level][x][y]): rl.DrawTexture(tiles[levels[level][x][y][i]], x*64, y*64, WHITE) # [ & ] change selected tile - also use T and mouse if rl.IsKeyPressed(rl.KEY_LEFT_BRACKET): placeTile-=1 if placeTile==-1: placeTile=87 if rl.IsKeyPressed(rl.KEY_RIGHT_BRACKET): placeTile+=1 if placeTile==88: placeTile=0 # place tile on bottom layer if rl.IsKeyPressed(rl.KEY_SPACE): while len(levels[level][int(playerPos.x/64)][int(playerPos.y/64)]) < 1: levels[level][int(playerPos.x/64)][int(playerPos.y/64)].append(0) levels[level][int(playerPos.x/64)][int(playerPos.y/64)][0] = placeTile # place tile on mid layer if rl.IsKeyPressed(rl.KEY_ONE): while len(levels[level][int(playerPos.x/64)][int(playerPos.y/64)]) < 2: levels[level][int(playerPos.x/64)][int(playerPos.y/64)].append(0) levels[level][int(playerPos.x/64)][int(playerPos.y/64)][1] = placeTile # place tile on top layer if rl.IsKeyPressed(rl.KEY_TWO): while len(levels[level][int(playerPos.x/64)][int(playerPos.y/64)]) < 3: levels[level][int(playerPos.x/64)][int(playerPos.y/64)].append(0) levels[level][int(playerPos.x/64)][int(playerPos.y/64)][2] = placeTile # if T held down then show the sprite sheet if rl.IsKeyDown(rl.KEY_T): i=0 for y in range(0,8): for x in range(0,11): rl.DrawTexture(tiles[i], x*64, y*64, WHITE) rl.DrawText(rl.TextFormat(b"i: %i",ffi.cast("int", i)), x*64, y*64, 20, BLACK) i+=1 # mouse selects tile mx = int(rl.GetMouseX()/64) my = int(rl.GetMouseY()/64) if mx >= 0 and mx < 11 and my >= 0 and my < 8: rl.DrawRectangleLines(mx*64,my*64,64,64,RED) placeTile = (my * 11) + mx # save if rl.IsKeyPressed(rl.KEY_S): text = json.dumps(levels, sort_keys=False, indent=4) with open('levels.json', 'w') as f: print(text, file=f) rl.TraceLog(rl.LOG_INFO,b"SAVED!") # draw the player rl.DrawTexture(tiles[playerFrame], int(playerPos.x), int(playerPos.y), WHITE) rl.DrawFPS(10, 10) #rl.DrawText(f"Frame: {int(frame)}".encode('UTF-8') , 10, 28, 20, MAROON) rl.DrawText(rl.TextFormat(b"Frame: %i",ffi.cast("int", frame)), 10, 30, 20, MAROON) rl.DrawText(rl.TextFormat(b"a fr : %i",ffi.cast("int", aFr)), 10, 60, 20, MAROON) rl.DrawText(b"PLACE", 10, 90, 20, WHITE) rl.DrawText(rl.TextFormat(b"tile: %i",ffi.cast("int", placeTile)), 10, 120, 20, WHITE) rl.DrawText(rl.TextFormat(b"level: %i",ffi.cast("int", level)), 10, 216, 20, WHITE) rl.DrawTexture(tiles[placeTile], 16, 150, WHITE) rl.EndDrawing() for i in tiles: rl.UnloadTexture(i)