Some 2D Space Shooter

Greetings,

I realized that with PacMan being copyright, it’s a bit tough to share to showcase the potential for making games in SuperCollider.

Here’s another crack at making some 2D games I started today. This one is the beginning of a top down, 2D outer space shooter that has no enemies or goals yet. Note - I have absolutely no idea what the unicode for space is on Linux or Windows. 20 was my best guess for Windows, but this works*** on a Mac.

Enjoy and feel free to reuse the code for whatever.

***with some obvious bugs in how the laser beams behave :joy:

(
s = Server.local;
s.waitForBoot({
var title = "SuperSpaceCollider";
var winW = 480, winH = 640;
var win = Window.new(title, Rect(300,100,winW,winH), false)
.front
.onClose_({Window.closeAll});

var keycodes = Platform.case(
	//<-, ->, ↓, ↑
	\osx, {[123,124,125,126,49]},
	\linux, {[65361,65363,65364,65362]},
	\windows, {[37,39,40,38,20]}
);
	
var numLasers = 0;

var movementSpeed = 3;

var playerDir = Set(1);

var firing = false;

//colors
var
brown = Color.new255(216, 145, 85),
pink = Color.new255(252, 180, 255),
red = Color.new255(252, 0, 0),
salmon = Color.new255(252, 180, 170),
orange = Color.new255(252, 180, 85),
yellow = Color.new255(252, 252, 0),
green = Color.new255(0, 252, 0),
teal = Color.new255(72, 180, 170),
cyan = Color.new255(0, 252, 255),
blue = Color.new255(72, 180, 255),
indigo = Color.new255(36, 36, 255),
white = Color.white,
black = Color.black,
clear = Color.clear;

var spriteH=35, spriteW=35;

var playerX=winW/2-spriteW, playerY=winH;
	
		var lasers = 0, laserX = playerX, laserY = playerY, shipScale=1;

var board = UserView(win, win.view.bounds)
	.background_(Color.blue(0.1));


var playerSprite = UserView(board, win.view.bounds)
.animate_(true)
.drawFunc_({
		var cockpitH = spriteH/3, cockpitW = spriteW/7;
		var numLines = 4;
	//movement
	playerDir.do({
		arg item;
	case
	{item == (\left)} {playerX=playerX-movementSpeed}
	{item == (\right)} {playerX=playerX+movementSpeed}
	{item == (\down)} {playerY=playerY+movementSpeed}
	{item == (\up)} {playerY=playerY-movementSpeed};
	});
	playerY=playerY.clip(0,win.view.bounds.height-spriteH);
	playerX=playerX.clip(spriteW.neg/2, win.view.bounds.width-(3/2*spriteW));
	Pen.translate(playerX+(spriteW/2), playerY);

	//draw
			
		Pen.moveTo(spriteW/2 @ 0).scale(shipScale, 1);
	//wings
	2.do({
		arg i;
			var ratio = (1/2);
		Pen
			.moveTo(spriteW/2 @ (7*spriteH/8))
			.lineTo(i*spriteW @ spriteH)
			.lineTo(i*spriteW @ (15*spriteH/16))
			.quadCurveTo(spriteW/2 @ 0, spriteW/2 @ (spriteH*ratio))
		.fillColor_(Color.grey(0.4))
		.strokeColor_(Color.grey(0.3))
		.draw(3);
	});
		
		//decor
	numLines.do({
		arg i;
		Pen
			.line(spriteW/numLines*(i+0.5) @ spriteH, spriteW/numLines*(i+0.5) @ (7*spriteH/10))
		.strokeColor_(Color.grey)
		.width_(2)
		.stroke
	});
		
			//decor
	2.do({
		arg i;
		Pen
			.line(i*spriteW @ spriteH, i*spriteW @ (6*spriteH/10))
		.strokeColor_(Color.grey)
		.width_(3)
		.stroke
	});
		
		//body
	2.do({
		arg i;
		Pen
		.moveTo((spriteW/2) @ 0)
		.quadCurveTo(spriteW/2 @ (spriteH), spriteW/4+(i*spriteW/2) @ (spriteH/2))
		.width_(1)
		.fillColor_(Color.grey(0.5))
		.strokeColor_(Color.grey)
		.draw(3)
	});
		
		//cockpit
		Pen.addOval(Rect(spriteW-cockpitW/2, (4*spriteH/10), cockpitW, cockpitH))
			.fillColor_(white)
		.strokeColor_(Color.grey(0.7))
		.draw(3);
		
		//propulsion
	5.rrand(12).do({
		arg i;
		Pen.color_([cyan,blue,indigo, white].choose)
		.line(17.5 @ (7*spriteH/8), 15.rrand(20) @ ((1*spriteH/2).rand + (7*spriteH/8)))
		.width_(3.rand)
		.stroke;
	});
		
})
.background_(clear);
	
	var laserView = UserView(playerSprite, win.view.bounds)
	.animate_(true)
	.drawFunc_({
		case 
		{firing == true}
		{laserX.do({
			arg item, i;
			laserY = laserY-4;
			Pen.line(item@laserY, item@(laserY+5)).width_(3).color_(red).stroke});
		};
	});

		//sounds
	var pow = {
		var num = 3.rrand(7);
		Pan2.ar(
		Mix.ar(
			num.collect({
			arg i;
		LFPulse.ar(100.rrand(200)*Line.ar(4,0.5,0.2), iphase:0.0, width:0.1.rrand(0.9)*SinOsc.ar(2, (pi/2).rand),
			mul:EnvGen.ar(Env.perc(0.001,0.5,curve:-4), doneAction:2))
		}))/num,
			playerX.linlin(0, winW, -1.0, 1.0))};

	View.globalKeyDownAction_({
		arg view, char, mod, uni, keycode;
		case
	{keycode == keycodes[0]} {playerDir.add(\left);}
		{keycode == keycodes[1]} {playerDir.add(\right);}
	{keycode == keycodes[2]} {playerDir.add(\down);}
	{keycode == keycodes[3]} {playerDir.add(\up);}
		{keycode == keycodes[4]} {pow.play; laserX = [playerX+(0.5*spriteW), playerX+(1.5*spriteW)]; laserY = playerY+(spriteH/2); firing = true; numLasers=numLasers+1}
		{uni == 27} {win.close; win.refresh};
	});
	View.globalKeyUpAction_({
		arg view, char, mod, uni, keycode;
		case
	{keycode == keycodes[0]} {playerDir.remove(\left);}
	{keycode == keycodes[1]} {playerDir.remove(\right);}
	{keycode == keycodes[2]} {playerDir.remove(\down);}
	{keycode == keycodes[3]} {playerDir.remove(\up);}
	{keycode == keycodes[4]} {numLasers=numLasers}
	});
		win.refresh;
});
)
4 Likes

This is great. The level of detail for the sprite and the laser sounds is much appreciated. I can’t wait to play the whole thing when it is “done”.

Sam

Just tried in Linux and Windows – space is 32 in both. (“20” sounds like hexadecimal – 0x20 = 32.)

It’s slick!

Also try making a SynthDef for “pow” instead of playing a function (better for timing, and efficiency).

hjh

Question James, how can I get playerY to set the value of Pan2.ar from a SynthDef? Would I just call Synth.set from drawFunc_({})? And I was also really unsure about iterating with rrand in a SynthDef… This isn’t possible right? I agree about the SynthDef though, I really should just pick a number and call it a day probably.

Thanks in advance,
JS

EDIT: Never mind, I got it :slight_smile:

Hi Mjsyts,

this is great and I love seeing someone interested in making games! Me too.

I’ve been writing a game engine in Supercollider for the last 2 years and I’m getting close to putting something out. Maybe this is something that might help you.

neil : )

4 Likes

Absolutely! Are you on GitHub? I’ve found this to be a lot of fun. I was really uncomfortable with GUI when I started and I was getting tired of making excuses about it.

Yes the project is on GIT hub but its private at the moment.
Let me check I haven’t got any copyrighted assets in it before I go public.

1 Like

The first playable version is complete! Updates will likely be forthcoming to make it more difficult and fine tune a few game mechanics, but I am thrilled at how well it runs so far.

2 Likes

Amazing work!! Thanks for sharing!