Log In  


I've been trying ways to take a specific sprite and replace all of them on the map with another sprite.

Here are two methods I figured out.

Cart #set_all1-0 | 2023-02-24 | Code ▽ | Embed ▽ | No License
1


This here is the first method and the one I use in my Pico-8 game "Marley The Adventurer". Probably the most simple and reliable method. It just scans all tiles one by one every time it wants to swap and immediatly replaces it by the other tile if the tile matches the one specified. It uses almost no extra RAM, but a lot of CPU usage. And if one of the tiles gets removed or if one gets added it will still react properly and replace the new tiles as well.
The function takes two parameters: set_all1(current_tile_sprite, new_tile_sprite)

Now for the next one

Cart #set_all2-0 | 2023-02-24 | Code ▽ | Embed ▽ | No License
1


This method works by scanning the whole map when you run the cart (which may lead to longer loading times) and stores the sprites coordinates into a variable. The tile replacing function simply reads the coordinates that were in memory and replaces them with a given sprite. This method is good when you have few tiles to replace, as it takes just a bit more RAM to save a lot of CPU, but if you want to replace a sprite that is drawn almost all over the map, this method will save a tiny bit of CPU and take a lot more RAM. It also takes more code and has the downside of not responding to map changes: it will still try to replace the coordinates in memory even if you made changes to the map.

The function that puts the sprites in memory:
sprites_in_memory = get_all2(sprite)

The function that sets a new sprite:
set_all2(sprites_in_memory, new_sprite)

What other ways do you have in mind? I'm kind of new to Pico-8 and I'd like to know what you would come up with.

P.S. There seems to be a bug with the amount of memory shown on the browser

1


@bilodau:

Consider changing only what the player can see. That would run faster. If you need help coding that, let me know and I will be happy to assist.


@dw817 I've been thinking about that too. And I'd really appreciate your help to help me code it.


Alright, lemme think for a second, @bilodau ...

First off be aware that if you use mapper that exceeds or is at 32 vertical, that cuts into your 128-255 sprites.

I'm not going to use that space. I'll just use the 0-127 horizontally and 0-15 vertically.

Cart #scms_02_24_23-0 | 2023-02-25 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA


I prefer using an array for the map. This makes it easy to add various properties for tiles, and change them dynamically.

Cart #mezudaduso-0 | 2023-02-25 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA


@binaryeye:

The map doesn't scroll to new data ... pretty sure that's a requisite.


1

bilodau: instead of looping over the map and using mset, have you thought of changing the sprite data with sset?
https://www.lexaloffle.com/dl/docs/pico-8_manual.html#SSET


That's a very good suggestion, @merwok !

@bilodau, can you see how to do that on your own - or would you like to see code for it ?


@dw817 @binaryeye @merwok I feel overwhelmed by all this information and don't understand much.


1

Hi @bilodau:

Well I think @merwok is definitely on to something. Here now, try out this code and examine the sprites. This is probably the first map in Pico-8 that takes advantage of 256-colors. :)

Here, I'll also document this one completely. Check the source-code for details.

Cart #hemumiyeso-0 | 2023-02-25 | Code ▽ | Embed ▽ | No License
1

Check the CPU usage. Using MEMCPY() for many sprites is actually faster than manually using SSET() for a single 8x8 sprite.

Runs best offline where it does not flicker at all - at least for me.


@dw817 I made this according to what @merwok said. Is that what you meant, @merwok?

Warning: contains flashing images

Cart #setall_v3-1 | 2023-02-25 | Code ▽ | Embed ▽ | No License


yes, that’s using sset to change sprites used in the map.
the demo could be improved by changing the sprites when a button is pressed ;)

as was pointed out, using memcpy will be more efficient than using sget, as memcpy can set 8 pixels in one call. it’s a bit more advanced but worth learning!


I really appreciate your help, guys. But I prefer not use anything fancy as I'm just a beginner.


1

Hi @bilodau. Let me write a cart using only SGET() and SSET() then if that is your preference.

Cart #dotidedayo-0 | 2023-02-26 | Code ▽ | Embed ▽ | No License
1

Examine the source-code. If that's not clear I can document each line.


I'll try to understand all of this tomorrow @dw817.
Thanks for the help.


@dw817 I understand now. I took the time to read and I understand. Thank you so much for the help you're an amazing community ❤


You are very welcome, @bilodau. I'm getting at the level I need to write comments in my code to help me keep my thoughts in order. :)

Glad to assist. Here's hoping your next cart is nothing short of awesome !


@dw817 Marley The Adventurer is not done yet! I imagine a new level, which will probably be a pain to code. But still, it's not over!

By the way... I also forget to write these comments a lot...


Hey, @dw817,

I made this function for the purpose of replacing a sprite with sset. Do you think you can make it work with memcpy and that the function still uses the same parameters?

function replace_sprite(os,ds,f)
	-- os: original sprite
	-- ds: destination sprite
	-- f: include flags

	local os_x = (os%16)*8
	local os_y = os \ 16 * 8
	local ds_x = (ds%16)*8
	local ds_y = ds \ 16 * 8
	for y = 0,7 do
		for x = 0,7 do
			sset(ds_x+x,ds_y+y,
			sget(os_x+x,os_y+y))
		end
	end

	if f then
		fset(ds,fget(os))
	end
end

Thanks in advance,

bilodau

P.S. According to my tests, each byte from 0x0 to 0x0fff represent two pixels on the sprite sheet. Am I right?


Hi @bilodau:

I am on the downstairs computer now. And ...

Nope, a single pixel for Pico-8 uses 4-bits of data, not 8. So if you were to poke 0x6000 or 24576 a value of 0xFF or 255 which is 8-bits then it would be two "Salmon" (color #15) pixels that appear in the top-left-hand corner of the screen.

If you poke something that is 3-bytes or more, it spills into the next horizontal frame. So MemCpy() doesn't really do you a big advantage unless you are shifting MORE THAN ONE sprite at a time.

Your best results for 16-sprites for instance will be to use the first row (#0 if you count them 0 to 15) - the first row would contain the sprite you want to have appear. The second row #1 to contain the image of the sprite 1st frame and the third row #2 to contain the image of the sprite 2nd frame.

Then simply use a timer to animate them in sequence. If you want to animate at different times, you can do this.

Suppose you want to have 8-sprites animated fairly quickly and the 8 after it to animate somewhat slower. You could have this for that:


Cart #sa2ss-4 | 2023-03-07 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

(v04 03-06-23)
TO LOAD THIS PICO-8 CART, in immediate mode, type: load #sa2ss


So here are 16-sprites that don't animate, the first is just a black square. Then you have 8 that do animate, slow, and 8-more that animate a bit faster. 2 frames per animation sprite cel in all. Uses a simple 128x16 drawing map field. Use LEFT and RIGHT arrow keys to scroll through it.

If you don't split the animation as I am doing now and in fact have 16-sprites across to animate at one speed and 16-more sprites to animate at a different speed, the code would run faster and cleaner and use less CPU overall.


a pixel is 4 bits, but a sprite is more than one pixel, so it’s still efficient to poke 4 bytes at a time to set 8 pixels, or memcpy 4 bytes (one would have to compare the token and cpu costs to pick either, or coding simplicity if one feels easier to understand)


I know it's late, but I finally made this more optimized function to replace a sprite in memory.

Here the replace_spr function replaces a sprite in memory for another. And the swap_spr function swaps two sprites in memory.

function get_spr_add(s)
	return s<<2 & 0x3c |
	s<<5 & 0x1e00
end

-- replaces a sprite in
-- memory with another.
-- overwites destination sprite
-- needs get_spr_add
function replace_spr(ds, ss, f)
	-- ds: destination sprite
	-- ss: source sprite
	-- f: include flags

	local sa = get_spr_add(ss)
	local da = get_spr_add(ds)

	for i = 0, 448, 64 do
		memcpy(da+i, sa+i, 4)
	end

	if (f) fset(ds,fget(ss))
end

-- swaps two sprites in memory
-- needs get_spr_add
function swap_spr(s1, s2, f)
	-- f1 and f2: sprites to swap
	-- f: include flags

	local a1 = get_spr_add(s1)
	local a2 = get_spr_add(s2)

	for i = 0, 448, 64 do
		local t = peek4(a2+i)
		memcpy(a2+i, a1+i, 4)
		poke4(a1+i, t)
	end

	if f then
		local t = fget(s2)
		fset(s2, fget(s1))
		fset(s1, t)
	end
end


[Please log in to post a comment]