Log In  


Cart #44669 | 2017-09-27 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
35

Step into cyberspace with cutting-edge 3D vector graphics. Battle tanks with your triangular pew-pew cannon. Explode into a number of polygons.

This is an homage to Specter VR, which came packaged with my Macintosh Performa back in the day.
https://www.mobygames.com/game/spectre

Features:
-60 fps
-Motion blur effects
-Blue/Red 3D glasses mode (it actually works)
-Multiple camera modes
-An attempt at writing title music

Issues:
Gameplay is a bit, well, simple. Basically drive around shooting and being shot at.

--Electric Gryphon

35


Awesome Electric Gryphon!!! you RULEZ! :D


Yes!
Anaglyph 3d is my favorite gimmick!

(If anyone ever made a game with anaglyph and a light gun I'd never do anything else.)

Without digging through the whole code, How are you blending the two channels?


Oh, I see, You're flickering frames at 60fps. I wonder if this will work on PocketCHIP.

Trying it out here on the web player, there might be a bug where after you die it drops back down to 30fps, because the stereographics gets really jittery after I die and go back to the main menu.


Hi apLundell,

I think I have a memory leak to hunt down / objects that are not getting deleted at game over. Time to snoop around...

3D glasses light gun game you say? Hmmm... I wonder if my Desert Lead game would work at 60 fps.

Update: Fixed the memory leak. Framerate shouldn't drop between games.


I see that you are using a lot the for x in all(set) construct.
My benchmark indicates it is mucho slower than for i=1,#set do...end
Any reason you are sticking to this construct?

For info, my dynamic list pattern is the following:

local set={c=0}
function set:add(elt)
 self.c+=1
 self[self.c]=elt
end
function self:apply(fn)
 local n=self.c
 self.c=0 -- virtually clear dataset
 for i=1,n do
  local elt=self[i]
  if fn(elt) then
   self.c+=1
   self[self.c]=elt
  end
 end
end

It's really faster to repack the list then it is to to use in/all?


In places where I needed to optimize speed, I have mostly switched to: "for i=1, #set do"
But I'll still use the: "for item in all(list) do" construct initially because it reads more clearly, and it handles deletions dynamically out of the box. (I'll keep your snippet in mind though if I need the boost.)


I see that you are using a lot the for x in all(set) construct.
My benchmark indicates it is mucho slower than for i=1,#set do...end

@freds72 - This is good to know! I've been using the for..all() to save on tokens but in cases where token space is available, great to know the other loop syntax is faster.


Sorry to continue derailing the thread on particle pattern, but here a crude benchmark for 128 particles:

  • mode 1: my "reuse" collection pattern :)
  • mode 2: standard add/del pattern
  • mode 3: memory backed collection (0x4300)

Results: my pattern wins :), close second add/del (but slightly unstable cpu usage), last memory (disapointed!)
Complementary screenshots (just because) and code:


Electricgryphon: now that I know a pico-8 god laid his eyes on my code, I can die a happy man :)

"Bench" code:

-- mini benchmark to compare various collection patterns
-- coll: index buffer
-- parts: add/del
-- memparts: index buffer using user memory

-- global time
local time_t=0
-- target particle count
local n=128

local coll={c=0}
function coll:len()
	return coll.c
end
function coll:spawn(x,y)
	coll.c+=1
	coll[coll.c]={
		x=x,y=y,
		t=time_t+120+rnd(16),
		dx=rnd(2)>1 and 1 or -1,
		dy=rnd(2)>1 and 1 or -1}
end
function coll:update()
	local k=coll.c
	coll.c=0
	for i=1,k do
		local p=coll[i]
		if p.t<time_t then
		else
			p.x+=p.dx
			if(p.x<1 or p.x>127) p.dx=-p.dx
			p.y+=p.dy
			if(p.y<1 or p.y>127) p.dy=-p.dy
			coll.c+=1
			coll[coll.c]=p
		end
	end
end
function coll:draw()
	for i=1,coll.c do
		local p=coll[i]
		pset(p.x,p.y,7)
	end
end

local parts={}
function parts:len()
	return #parts
end
function parts:spawn(x,y)
	add(parts,{
		x=x,y=y,
		t=time_t+120+rnd(16),
		dx=rnd(2)>1 and 1 or -1,
		dy=rnd(2)>1 and 1 or -1})
end

function parts:update()
	for p in all(parts) do
		if p.t<time_t then
			del(parts,p)
		else
			p.x+=p.dx
			if(p.x<1 or p.x>127) p.dx=-p.dx
			p.y+=p.dy
			if(p.y<1 or p.y>127) p.dy=-p.dy
		end
	end
end

function parts:draw()
	for p in all(parts) do
		pset(p.x,p.y,7)
	end
end

function readbyte(m)
	return peek(m)-128
end
function writebyte(m,i)
	poke(m,i+128)
end
function readint(m)
	return bor(peek(m),shl(peek(m+1),8))
end
function writeint(m,i)
	poke(m,i)
	poke(m+1,shr(i,8))
end
local c=0
local xm=0x4300
local ym=xm+n*2
local tm=ym+n*2
local dxm=tm+n*2
local dym=dxm+n
local memparts={}
function memparts:len()
 return c
end
function memparts:update()
	local x,y,t,dx,dy
	local ii=c-1
	c=0
	for i=0,ii do
		x=readint(xm+i*2)
		y=readint(ym+i*2)
		t=readint(tm+i*2)
		if t>time_t then
			dx=readbyte(dxm+i)
			dy=readbyte(dym+i)
			x+=dx
			if(x<1 or x>127) dx=-dx
			y+=dy
			if(y<1 or y>127) dy=-dy
			writeint(xm+c*2,x)
			writeint(ym+c*2,y)
			writeint(tm+c*2,t)
			writebyte(dxm+c,dx)
			writebyte(dym+c,dy)
			c+=1
		end
	end
end
function memparts:draw()
	local x,y,t
	for i=0,c-1 do
		x=readint(xm+i*2)
		y=readint(ym+i*2)
		t=readint(tm+i*2)
		pset(x,y,7)
	end
end
function memparts:spawn(x,y)
	writeint(xm+c*2,x)
	writeint(ym+c*2,y)
	writeint(tm+c*2,time_t+120+rnd(16))
	writebyte(dxm+c,rnd(2)>1 and 1 or -1)
	writebyte(dym+c,rnd(2)>1 and 1 or -1)
	c+=1
end

local mode={
	coll,parts,memparts}
local mode_i=1
local particles=mode[mode_i]
local perf={}
local perf_ramp={8,11,5}
local perf_name={"cpu","mem","n"}
function _update60()
	if btnp(4) or btnp(5) then
		mode_i=(mode_i+1)%(#mode+1)
		if(mode_i==0) mode_i=1
		particles=mode[mode_i]
	end
	if particles:len()<n then
		particles:spawn(rnd(127),rnd(127))
	end

	particles:update()
	perf[time_t%64+1]={
		stat(1),
		stat(0)/1024,
		particles:len()/n}

	time_t+=1
end

function _draw()
	cls(0)
	particles:draw()

	print("mode:"..mode_i,2,120,7)

	rectfill(0,0,64,32,1)
	local t=64-time_t%64
	line(t,0,t,32,6)
	for k=1,3 do
		local scale=32
		if(perf[1][k]<0.5) scale=64
		if(perf[1][k]<0.25) scale=128
		color(perf_ramp[k])
		for i=1,#perf do
			local p=perf[i]
			pset(64-i,32-scale*p[k])
		end
		print(perf_name[k]..":"..(flr(1000*perf[1][k])/10).."%",2,28+6*k)
	end
end


Do 3d glasses actually work on this?


Yes. Not perfectly, but yes.

I tried it with Red/Cyan glasses. The 3d effect was ok. There was some ghosting though. Playing for too long might give you a headache.

It might work better with Red/Green glasses, but those are harder to find, I don't happen to have any on hand.

The depth effect itself is a bit subtle. The difference between near and far objects isn't too extreme. I usually prefer subtle 3d over crazy in-your-face 3d effects, but I think I would have exaggerated this one a little more.

Short version : It works well enough to be a fun gimmick. But don't throw away your Virtual Boy just yet.


Yeah Red/Green glasses are hard to find these days as the accepted new standard is cyan/red. (Even Minecraft's Anaglyph mode uses the C/R option.)

But there is hope!

https://www.aliexpress.com/store/product/EastVita-Red-Blue-3D-Glasses-Anaglyph-Framed-3D-Vision-Glasses-for-Game-Stereo-Movie-Dimensional-Glasses/2882079_32816293677.html

Wherever you shop and whatever kind you get though, You'll find the plastic or metal ones are far superior to the crappy cardboard 'tossaway' ones. Those use cellophane which is not durable, And gets dirty and crinkles easier.

Not meaning to distract, Just anaglyph is awesome. Also be interesting to see this effect used with the "Poculous chip" mod in mind.

And if anyone has an old set of 'active shutter' style glasses and a CHIP a fun project could be timing the refreshes to flash red and cyan filters at the right hz to work with those too through the gpio pins and some tricky timing with the DRAW function. I used to have the Sega set and let me tell you, Zaxxon was a whole other beast playing with those on. X3



[Please log in to post a comment]