yet another Map Editor (or how i'd design a game)

ZX80, ZX 81, ZX Spectrum, TS2068 and other clones
Post Reply
Timmy
Well known member
Posts: 208
Joined: Sat Mar 10, 2012 4:18 pm

yet another Map Editor (or how i'd design a game)

Post by Timmy »

Since everyone here is doing engines and editors, I thought I could share how I think I would make a small game in 2021.

Here are the files I'm including in this post: https://www.sendspace.com/file/tnvjg8
Note: click the blue download button and sorry for the ads.

1) Draw some tiles and sprites.

Use your favourite image drawing program for that.
I could make a simple sprite/tiles editor but I am using Gimp for many years now.
You can find an image of a tileset in the zip file. Most of the demo tiles there are tiles I used before in other (released) games.

2) Use a map editor.

I used Mappy and Tiled before, and I'm not happy with both, so I made my own. See the file mentioned at the beginning of the post.

Image

This is just a really small windows program. If you use linux, maybe wine will work on it.
This is just yet another (minimal) map editor I've made using code from other map editors I've built in the past.
Took me a week to write it, and it still doesn't do much. :lol:

All you have to do here is draw your game map, including everything.
You can draw on the left grid using the left mouse button. You select the tile to draw by using the mouse on the right, smaller grid, or using the right mouse button on the drawing grid.

The editor (and the current tileset) consider the first 48 tiles (1 tile = 16x16 pixels) as visible tiles, and everything above those are markers.
Markers are just basically not visible and is depending on your code what they do. You might not even need them at all.

The point here is that I would be drawing the map of my game here. Note that at this point, I have still not written or used any code.

The editor exports the map of the rooms as DEFBs so that I can include it directly inside my game. It also exports the first 48 tiles as tile data DEFBs to be included in the C code, too.

3) Write the code.

Now I start writing the code that is needed to finish the game, and not more.
Then I compile it, and I've included an example tap file in the zip file.

Image

(Obviously this is just a demo so I've left out most of the functionalities. I wrote this stub code by starting with some really old game and then removed a lot of functionality, so the code took me only a few hours to "write".)

I will talk about the stub code in a separate post later.

4) Repeat until game finished.

Play, test, add/change stuff that you don't like, and improve. Until the game is finished.
Try not to add any new functionalities that is not on your map!
Timmy
Well known member
Posts: 208
Joined: Sat Mar 10, 2012 4:18 pm

Re: yet another Map Editor (or how i'd design a game)

Post by Timmy »

Here's the code I've used for the demo.

Note that I'm NOT planning to write more explanations about this code for now. Almost every part in this source code have been discussed before in this forum separately. Also I don't want to spend 5 full days just to explain the source code. (Sorry, I write really slowly, and it takes a lot of text to explain. And I would like to write more code right now. :) )

What is interesting to know is that the exported files in the map editor is being included in this source code with #include, and then compile this to make the demo, so any changes in the map can be incorporated into the demo within seconds. :cool:

Code: Select all

// zcc +zx -zorg=26000 -create-app -lsp1 w3.c -o w3.bin -pragma-define:fputc_cons=0 -pragma-define:CRT_INITIALIZE_BSS=0 -pragma-define:CRT_ENABLE_STDIO=0 -pragma-define:CRT_DISABLELOADER=1

/*
*
* This is the code I used for the demo.
* Most of this code is lifted from the Forest Raider Cherry source code
* Some parts of code are updated to be used for z88dk 2.1, like fastcalls and joystick functions.
*
* Note: This is not a complete game, because I'm still refactoring this really old code.
* Still, this is probably already a lot to read and grasp, if you're new to z88dk and sp1.
*
* -- Timmy, March 2021
*
*/

#include <sprites/sp1.h>
#include <malloc.h>
#include <input.h>
#include <spectrum.h>
#include <stdlib.h>                 // for bpoke(), wpoke(), rand()
#include <string.h>                 // for memset()
#include <im2.h>

#include "font1.c"
#include "demo.txt"
#include "demotiles.txt"

//////////////////////////////////////////////////////////////


#pragma output STACKPTR=53248   // place stack at $d000 at startup
long heap;                      // malloc's heap pointer

// malloc and free taken from the sample code.
void *u_malloc(uint size) { return malloc(size); }
void u_free(void *addr) { free(addr); }


//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

uchar levelb[28*20*3];
struct sp1_Rect levelbrect    = {2, 2, 28, 20};             // full screen

uchar roommap[140];

// Clipping Rectangle for Sprites
struct sp1_Rect cliprect    = {2, 2, 28, 20};             // full screen
struct sp1_Rect clipscreen  = {0, 0, 32, 24};             // full screen
struct sp1_Rect specialrect = {3, 3,  2,  2};            
struct sp1_Rect miscrect    = {0, 2, 28,  2};
struct sp1_Rect bonusrect   = {8, 18, 9,  8}; // format is y,x,w,h!

#define maxsprites 5

// Table Holding Movement Data for Each Sprite
struct sprentry {
   struct sp1_ss  *s;                            // sprite handle returned by sp1_CreateSpr()
   uchar	x;
   uchar	y;
   uchar	specie;							     // 1 moves horizontal, 2 moves vertical
   uchar	counter;							 // wordt gebruikt om de state te bepaelen.
   char           dx;                            // signed horizontal speed in pixels
   char           dy;                            // signed vertical speed in pixels
};

struct sprentry sprtbl[maxsprites];
// Attach C Variable to data Declared in ASM at End of File

extern uchar sprite1[];
extern uchar tiledata[];
extern uchar mapdata[];
//uchar font1[368] ==> defined in font.c

void beep3a()
{
		#asm
			ei
			ld de, 10
			ld hl, 50
			call 0x03b5
//			di
		#endasm
}

void beep1()
{
	beep3a();
	sp1_Invalidate(&cliprect); // invalidate entire screen
	sp1_UpdateNow();             // draw screen now
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

//
// Set up Joysticks
// Can also be found in the example on z88dk.org
//
unsigned char joys;

uint (*joyfunc)(struct in_UDK *) __z88dk_fastcall;
struct in_UDK joykeys;

void setupkeyboard() 
{
	joykeys.fire  = in_LookupKey(' ');
	joykeys.left  = in_LookupKey('o');
	joykeys.right = in_LookupKey('p');
	joykeys.up    = in_LookupKey('q');
	joykeys.down  = in_LookupKey('a');

	joyfunc = in_JoyKeyboard;
}

void setupcursor() 
{
	joykeys.fire  = in_LookupKey('0');
	joykeys.left  = in_LookupKey('5');
	joykeys.right = in_LookupKey('8');
	joykeys.up    = in_LookupKey('7');
	joykeys.down  = in_LookupKey('6');

	joyfunc = in_JoyKeyboard;
}

unsigned char kempston_available = 0;

void choosejoystick() 
{
	kempston_available = zx_kempston();
	while (1)
	{
		joyfunc = in_JoyKeyboard;
		joykeys.fire  = in_LookupKey(' ');
		joys = (joyfunc) (&joykeys);
		if (in_FIRE & joys) return;
		joykeys.fire  = in_LookupKey('m');
		joys = (joyfunc) (&joykeys);
		if (in_FIRE & joys) return;
		joykeys.fire  = in_LookupKey('c');
		joys = (joyfunc) (&joykeys);
		if (in_FIRE & joys) { setupcursor(); return; }
		joyfunc = in_JoySinclair1;
		joys = (joyfunc) (&joykeys);
		if (in_FIRE & joys) return;
		joyfunc = in_JoySinclair2;
		joys = (joyfunc) (&joykeys);
		if (in_FIRE & joys) return;
		if (kempston_available)
		{
			joyfunc = in_JoyKempston;
			joys = (joyfunc) (&joykeys);
			if (in_FIRE & joys) return;
		}
	}
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

uchar sprite_attr;
uchar sprite_amask;

void sprite_setcolour(struct sp1_cs *c)
{
   c->attr_mask = sprite_amask;       // set the sprite character's attribute mask
   c->attr = sprite_attr;             // set the sprite character's attribute
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

void drawmap()
{
	sp1_PutTiles(levelbrect, levelb);
	sp1_Invalidate(&levelbrect);
}

int checklwall(uchar p)
{
	return (p==1);
}


//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

uchar levelnr;

void loadlevel(uchar lnr)
{
	uchar x, y; 
	static uchar* q;
	static int p;
	int r;
	int t;
	uchar rm;

	memset (levelb, 0, sizeof(levelb));

	q = &(mapdata[lnr*140]);
	rm = 0;
	
	for (y=0; y<10; y++)
	{
		p = y * 56 * 3;
		
		for (x=0; x<14; x++)
		{
			t = *q;
			roommap[rm++] = t;
			r = t*36 + 8;
			t = t*4 + 64;
			if (t>=256) { t = 64; r = 8; }
			
			{	
				// tiles
				levelb[p+1]       = t++;
				levelb[p+1+3]     = t++;
				levelb[p+1+28*3]  = t++;
				levelb[p+1+29*3]  = t;			
				
				// kleuren
				levelb[p]		= tiledata[r];
				levelb[p+3]		= tiledata[r+9];
				levelb[p+28*3]	= tiledata[r+18];
				levelb[p+29*3]	= tiledata[r+27];
			}
			p += 6;
			q ++;
		}
	}

	drawmap();
}

void __FASTCALL__ printstring2()
{
	#asm
.print2loop
	ld a, (hl)
	inc a
	ret z
	dec a
	rst 16
	inc hl
	jr print2loop
	#endasm
}

void __FASTCALL__ printlogo()
{
	#asm
	ld hl, pp2
	call _printstring2
	ret
.pp2
	defb 22, 0, 0
	defm "interactive demo, fire to start"
	defb 13
	defm "keys: up down left right - Timmy"
	defb 255
	#endasm
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////

main()
{
   uchar i,j;
   struct sp1_ss *s;
   struct sprentry *se;
   void *temp;
   uchar c;
   uchar scoreupdate=0;
   uchar playerx, playery;
   
   #asm
   di
   #endasm


   // needed for malloc
   heap = 0;                  // heap
   sbrk(23800, 2000);         // create memory from 24000-29000

   // setting up isr
   #asm
   di
   #endasm
 
   
   im2_Init(0xd000);                // place z80 in im2 mode with interrupt vector table located at 0xd000
//   memset(0xd000, 0xd1, 257);       // initialize 257-byte im2 vector table with all 0xd1 bytes
   #asm
   ld hl, 0xd000
   ld de, 0xd001
   ld (hl), 0xd1
   ld bc, 257
   ldir
   #endasm
   

   // POKE instructions at address 0xd4d4 (interrupt service routine entry)
   // instructions for EI; RETI
   *(unsigned char *)0xd1d1=0xfb;
   *(unsigned char *)0xd1d2=0xed;
   *(unsigned char *)0xd1d3=0x4d;

     
   #asm
   ei
   #endasm
   
   beep3a();

   // Set up joystick
   setupkeyboard();
   
   // Initialize SP1.LIB
   
   //zx_border(BLACK);
   //put (8*b) into the BORDCR system variable
	*(unsigned char *)23624=0;
   
   // The next line is very important. The game will not compile if it is removed.
   sp1_Initialize( SP1_IFLAG_MAKE_ROTTBL | SP1_IFLAG_OVERWRITE_TILES | SP1_IFLAG_OVERWRITE_DFILE, 
				   BRIGHT | INK_WHITE | PAPER_BLACK, ' ');

				   
   // Initialise Tiles
   for (i=0; i<192; i++)
   {
		sp1_TileEntry(64+i, tiledata+i*9);
   }

   // Font1 starts here
   sp1_TileEntry(33-32, font1);

   for (i=1; i<46; i++)
   {
		sp1_TileEntry(45-32+i, font1+i*8);
   }
   

   loadlevel(0);

   sp1_Invalidate(&cliprect);   // invalidate the center part
   sp1_UpdateNow();             // draw screen now
   
   #asm
   halt
   #endasm
   
   // Initialise sprites

   for (i=0; i<maxsprites; i++) 
   {
	   s = sprtbl[i].s = sp1_CreateSpr(SP1_DRAW_MASK2LB, SP1_TYPE_2BYTE, 3, 0, i);
	   sp1_AddColSpr(s, SP1_DRAW_MASK2, 0, 48, i);
	   sp1_AddColSpr(s, SP1_DRAW_MASK2RB, 0, 0, i);
   }; 
   
	// Setting colour of the player here
	
	sprite_attr  = 7 ;
	sprite_amask = 0xF8;                 // mask will keep everything except INK 
	sp1_IterateSprChar(sprtbl[0].s, sprite_setcolour);    // colour the sprite

	// END OF INITIALISATIONS PHASE

   while (1)
   {

menu:

   beep3a();

    printlogo();
	
   playerx = 128; playery = 88; levelnr = 0;
   loadlevel(levelnr);
   sp1_MoveSprPix(sprtbl[0].s, &cliprect, sprite1+96*(2+((playerx&2)>>1)), playerx, playery);

   sp1_Invalidate(&cliprect);   // invalidate the center part
   sp1_UpdateNow();             // draw screen now
   
   
    // Select which joystick to use
	joys = 0;
	while ((joys & in_FIRE) != in_FIRE)
	{
		choosejoystick();
	}
 
   
   while (1) 
   {                                  // main loop
		joys = (joyfunc) (&joykeys);
		
		if (joys & in_LEFT)  playerx -= 2;
		if (joys & in_RIGHT) playerx += 2;
		if (joys & in_UP)    playery -= 2;
		if (joys & in_DOWN)  playery += 2;
		
		if (playerx>256-32)
		{
			playerx = 16+4;
			levelnr += 1;
			loadlevel(levelnr);
		}
		
		if (playerx<16)
		{
			playerx = 256-32-4;
			levelnr -= 1;
			loadlevel(levelnr);
		}
		
		if (playery>192-32)
		{
			playery = 16+4;
			levelnr += 10;
			loadlevel(levelnr);
		}
		
		if (playery<16)
		{
			playery = 192-32-4;
			levelnr -= 10;
			loadlevel(levelnr);
		}
		
		i = 0;
		sp1_MoveSprPix(sprtbl[i].s, &cliprect, sprite1+96*(2+((playerx&2)>>1)), playerx, playery);
		#asm
		halt
		ld a, 2
		out (254), a
		#endasm
		
		sp1_UpdateNow();
		
		#asm
		ld a, 1
		out (254), a
		#endasm
		
   }  // end main loop

	}
   #asm
   ei
   #endasm

   return 0;
}

#asm

;
; SPRITE FORMAAT
;
; A C
; B D
;

  DEFB 255,    0,  255,    0,  255,    0,  255,    0
  DEFB 255,    0,  255,    0,  255,    0,  255,    0

._sprite1

#include "cherrysprites.c"

#endasm
fraespre
Member
Posts: 28
Joined: Mon Aug 19, 2019 8:08 pm

Re: yet another Map Editor (or how i'd design a game)

Post by fraespre »

Hi,

I checked your editor and it's quite interesting for me.
However I lack some features:
- How to generate new ymp maps from scratch.
- Change the screen dimensions.
- Nice to have => posibility to allow other tiles dimensions ... at same time :-p (8x8 , 16x16, 32x16)

Note: I know that last point is quite difficult because any of the famous Tile Editor haven't it.
Timmy
Well known member
Posts: 208
Joined: Sat Mar 10, 2012 4:18 pm

Re: yet another Map Editor (or how i'd design a game)

Post by Timmy »

Thanks, but for now I'm working on the sp1 code that uses the output of the editor.

I will revisit the editor later when I have more time. And I will add functionality if the engine permits.

Also, ymp file format will change in the future. I am storing every entry of it as words, but none of those numbers stored will be higher than 128 anyway.
fraespre
Member
Posts: 28
Joined: Mon Aug 19, 2019 8:08 pm

Re: yet another Map Editor (or how i'd design a game)

Post by fraespre »

Hi again Timmy,
As you said you are working in the sp1 output code. Let me comment a litle sugestion.

In my opinion, it would be convenient not only generate the DEFBs file, also to allow a Binary file generation. That's because some developers could want to make a data compression. And if you directly generate this format, you avoid them more conversions.
Timmy
Well known member
Posts: 208
Joined: Sat Mar 10, 2012 4:18 pm

Re: yet another Map Editor (or how i'd design a game)

Post by Timmy »

Thanks for you little suggestion. It was very enjoyable.

As I said before, it is an interesting idea and I will think about it, but not before I have at least finalised the exported text formats.

And you are right that "some developers could want to make a data compression," but I'm always more interested in people finishing games.
Post Reply