189 lines
10 KiB
GDScript
189 lines
10 KiB
GDScript
@tool
|
|
extends Node2D
|
|
const PLAYER = preload("res://games/robotron/art/player.png")
|
|
const HUMANS = preload("res://games/robotron/art/humans.png")
|
|
const ROBOTS = preload("res://games/robotron/art/robotrons.png")
|
|
const TITLE = preload("res://games/robotron/art/titlescreen_example.png")
|
|
const COLORS := [Color.WHITE,Color.YELLOW,Color("ff8000"),Color.MAGENTA,Color.GREEN,Color.RED,Color.BLUE]
|
|
# Small pixel font for HUD and instructions; artwork remains in supplied sheets.
|
|
const FONT := {
|
|
"A":"01110100011000111111100011000110001","B":"11110100011000111110100011000111110","C":"01111100001000010000100001000001111","D":"11110100011000110001100011000111110",
|
|
"E":"11111100001000011110100001000011111","F":"11111100001000011110100001000010000","G":"01111100001000010111100011000101111","H":"10001100011000111111100011000110001",
|
|
"I":"11111001000010000100001000010011111","J":"00111000100001000010000101001001100","K":"10001100101010011000101001001010001","L":"10000100001000010000100001000011111",
|
|
"M":"10001110111010110101100011000110001","N":"10001110011010110011100011000110001","O":"01110100011000110001100011000101110","P":"11110100011000111110100001000010000",
|
|
"Q":"01110100011000110001101011001001101","R":"11110100011000111110101001001010001","S":"01111100001000001110000010000111110","T":"11111001000010000100001000010000100",
|
|
"U":"10001100011000110001100011000101110","V":"10001100011000110001100010101000100","W":"10001100011000110101101011101110001","X":"10001100010101000100010101000110001",
|
|
"Y":"10001100010101000100001000010000100","Z":"11111000010001000100010001000011111","0":"01110100011001110101110011000101110","1":"00100011000010000100001000010001110",
|
|
"2":"01110100010000100010001000100011111","3":"11110000010000101110000010000111110","4":"00010001100101010010111110001000010","5":"11111100001000011110000010000111110",
|
|
"6":"01110100001000011110100011000101110","7":"11111000010001000100010000100001000","8":"01110100011000101110100011000101110","9":"01110100011000101111000010000101110",
|
|
"-":"00000000000000011111000000000000000",":":"00000001000010000000001000010000000","!":"00100001000010000100001000000000100",".":"00000000000000000000000000010000100"}
|
|
var sprites: Dictionary = {}
|
|
var game: Node
|
|
var badge: ImageTexture
|
|
|
|
func _ready() -> void:
|
|
game = get_parent().get_parent()
|
|
var player := PLAYER.get_image()
|
|
for f in range(3):
|
|
for d in range(4):
|
|
register("player%d_%d"%[d,f],player,Rect2i([0,7,14,23][d],[0,12,25][f],[5,5,7,7][d],[11,12,10][f]),Vector2i(8,12))
|
|
var human := HUMANS.get_image()
|
|
for f in range(3):
|
|
for d in range(4):
|
|
register("human0_%d_%d"%[d,f],human,Rect2i([0,9,19,30][d],[0,13,27][f],[8,8,9,8][d],12),Vector2i(10,14))
|
|
register("human1_%d_%d"%[d,f],human,Rect2i([0,9,18,28][d],[41,54,70][f],[8,8,10,10][d],12 if f==0 else 14),Vector2i(10,14))
|
|
register("human2_%d_%d"%[d,f],human,Rect2i([1,7,12,18][d],[85,96,108][f],[4,4,5,4][d],10 if f==0 else 11),Vector2i(6,12))
|
|
register("prog",human,Rect2i(0,120,11,11))
|
|
var robots := ROBOTS.get_image()
|
|
for f in range(3):
|
|
register("grunt%d"%f,robots,Rect2i(f*12,0,9,13),Vector2i(10,14))
|
|
register("hulk%d"%f,robots,Rect2i(14,[14,30,46][f],11,15),Vector2i(14,16))
|
|
register("brain%d"%f,robots,Rect2i(1,[141,157,174][f],12,15),Vector2i(14,16))
|
|
register("spheroid",robots,Rect2i(45,65,11,11))
|
|
register("enforcer",robots,Rect2i(39,79,11,11))
|
|
register("quark",robots,Rect2i(27,103,9,9))
|
|
register("tank0",robots,Rect2i(29,116,14,17))
|
|
register("tank1",robots,Rect2i(44,116,14,17))
|
|
register("tank2",robots,Rect2i(59,116,14,17))
|
|
register("shell",robots,Rect2i(0,134,7,7))
|
|
for i in range(6):
|
|
var key := "electrode%d"%i
|
|
register(key,robots,[Rect2i(1,220,7,7),Rect2i(11,220,7,7),Rect2i(21,220,7,7),Rect2i(32,220,7,7),Rect2i(49,220,7,7),Rect2i(84,220,7,7)][i])
|
|
var mask: Image = sprites[key].get_image()
|
|
for y in range(mask.get_height()):
|
|
for x in range(mask.get_width()):
|
|
var alpha := mask.get_pixel(x,y).a
|
|
mask.set_pixel(x,y,Color(1,1,1,alpha))
|
|
sprites[key] = ImageTexture.create_from_image(mask)
|
|
# Reuse the supplied title's exact Williams emblem as a tintable mask.
|
|
var emblem := TITLE.get_image().get_region(Rect2i(48,16,56,54))
|
|
emblem.convert(Image.FORMAT_RGBA8)
|
|
for y in range(emblem.get_height()):
|
|
for x in range(emblem.get_width()):
|
|
var c := emblem.get_pixel(x,y)
|
|
emblem.set_pixel(x,y,Color.WHITE if c.r>.8 and c.g>.8 and c.b>.8 else Color.TRANSPARENT)
|
|
badge = ImageTexture.create_from_image(emblem)
|
|
|
|
func register(key: String, image: Image, rect: Rect2i, canvas: Vector2i = Vector2i.ZERO) -> void:
|
|
var piece := image.get_region(rect)
|
|
if canvas!=Vector2i.ZERO:
|
|
var padded := Image.create(canvas.x,canvas.y,false,Image.FORMAT_RGBA8)
|
|
piece.convert(Image.FORMAT_RGBA8)
|
|
padded.blit_rect(piece,Rect2i(Vector2i.ZERO,piece.get_size()),Vector2i((canvas.x-piece.get_width())/2,canvas.y-piece.get_height()))
|
|
piece = padded
|
|
sprites[key] = ImageTexture.create_from_image(piece)
|
|
|
|
func sprite(key: String, point: Vector2, tint: Color = Color.WHITE) -> void:
|
|
var texture: Texture2D = sprites[key]
|
|
draw_texture(texture,(point-texture.get_size()*.5).round(),tint)
|
|
|
|
func text(value: String, point: Vector2, color: Color = Color.WHITE) -> void:
|
|
for i in range(value.length()):
|
|
if not FONT.has(value[i]): continue
|
|
var bits: String = FONT[value[i]]
|
|
for pixel in range(35):
|
|
if bits[pixel]=="1": draw_rect(Rect2(point+Vector2(i*6+pixel%5,pixel/5),Vector2.ONE),color)
|
|
|
|
func center(value: String,y: float,color: Color = Color.WHITE) -> void:
|
|
text(value,Vector2(roundf((292-value.length()*6)/2.0),y),color)
|
|
|
|
func direction_index(v: Vector2) -> int:
|
|
if absf(v.x)>absf(v.y): return 0 if v.x<0 else 1
|
|
return 3 if v.y<0 else 2
|
|
|
|
func draw_title() -> void:
|
|
draw_texture_rect(TITLE,Rect2(0,0,292,240),false)
|
|
# Animated emblems move around the perimeter, rather than freezing the screenshot.
|
|
draw_rect(Rect2(0,0,292,44),Color.BLACK)
|
|
draw_rect(Rect2(0,200,292,40),Color.BLACK)
|
|
draw_rect(Rect2(0,44,36,164),Color.BLACK)
|
|
draw_rect(Rect2(256,44,36,164),Color.BLACK)
|
|
var perimeter := 2.0*(252+200)
|
|
var shift: float = 0.0 if game.reduced_flashes else game.phase_time*24
|
|
for i in range(28):
|
|
var distance := fposmod(i*perimeter/28+shift,perimeter)
|
|
var p: Vector2
|
|
if distance<252: p=Vector2(20+distance,20)
|
|
elif distance<452: p=Vector2(272,20+distance-252)
|
|
elif distance<704: p=Vector2(272-(distance-452),220)
|
|
else: p=Vector2(20,220-(distance-704))
|
|
draw_texture_rect(badge,Rect2((p-Vector2(13,13)).round(),Vector2(26,26)),false,COLORS[(i+int(game.phase_time*2))%COLORS.size()] if not game.reduced_flashes else COLORS[i%COLORS.size()])
|
|
draw_rect(Rect2(108,158,76,10),Color.BLACK)
|
|
center("FREE PLAY",160,Color.BLUE)
|
|
|
|
func _draw() -> void:
|
|
if game==null: return
|
|
draw_rect(Rect2(0,0,292,240),Color.BLACK)
|
|
if game.phase=="title":
|
|
draw_title()
|
|
return
|
|
var accent: Color = COLORS[int(game.clock*3)%COLORS.size()] if not game.reduced_flashes else Color.RED
|
|
draw_rect(Rect2(4,16,284,216),Color.BLUE if game.phase in ["story","roster","scores"] else Color.RED,false,1)
|
|
text(str(game.last_score if game.demo else game.score),Vector2(52,4))
|
|
if game.phase in ["story","roster","scores"]:
|
|
center("FREE PLAY",233,Color.GREEN)
|
|
else:
|
|
for i in range(mini(maxi(0,game.lives-1),7)): sprite("player2_0",Vector2(134+i*10,8))
|
|
center("%d WAVE"%game.wave,233,Color.BLUE)
|
|
if game.phase=="story":
|
|
center("ROBOTRON: 2084",30,accent)
|
|
var lines := ["YOU ARE THE LAST HOPE OF MANKIND.","SAVE THE LAST HUMAN FAMILY!","MOVE AND FIRE IN EIGHT DIRECTIONS.","WASD MOVES ARROWS FIRE","RESCUE HUMANS FOR 1000 TO 5000","EXTRA LIFE EVERY %d POINTS"%game.extra_life_interval if game.extra_life_interval>0 else "BONUS LIVES DISABLED"]
|
|
for i in range(lines.size()):
|
|
if game.phase_time>i*.8: center(lines[i],56+i*22,COLORS[(i+2)%7])
|
|
sprite("player2_0",Vector2(114,205))
|
|
sprite("human0_2_0",Vector2(146,205))
|
|
return
|
|
if game.phase=="roster":
|
|
center("THE ROBOTRONS",30,accent)
|
|
var names := ["GRUNT","HULK","SPHEROID","ENFORCER","BRAIN","QUARK","TANK","PROG"]
|
|
var keys := ["grunt0","hulk0","spheroid","enforcer","brain0","quark","tank0","prog"]
|
|
for i in range(8):
|
|
sprite(keys[i],Vector2(48,54+i*20))
|
|
text(names[i],Vector2(76,50+i*20),COLORS[i%7])
|
|
return
|
|
if game.phase=="scores":
|
|
center("ROBOTRON HERO",60,accent)
|
|
center("HIGH SCORE",92)
|
|
center(str(game.high_score),110,Color.YELLOW)
|
|
center("PUSH START",176,accent)
|
|
return
|
|
for entity in game.entities:
|
|
if not entity.alive: continue
|
|
var frame := int(game.clock*8+entity.frame)%3
|
|
var key: String = entity.kind
|
|
match key:
|
|
"human": key="human%d_%d_%d"%[entity.human_type,direction_index(entity.velocity),frame]
|
|
"grunt","hulk","brain","tank": key+=str(frame)
|
|
"electrode": key+=str((game.wave+entity.id)%6)
|
|
"converting": key="prog"
|
|
var tint := Color.WHITE
|
|
if entity.kind=="converting": tint = accent
|
|
elif entity.kind=="electrode": tint = [Color.GREEN,Color.RED,Color.BLUE,Color.YELLOW,Color.MAGENTA,Color.CYAN][(game.wave-1)%6]
|
|
sprite(key,entity.position,tint)
|
|
if game.phase=="dead":
|
|
for i in range(8):
|
|
var offset: Vector2 = Vector2.from_angle(i*TAU/8)*(1.2-game.phase_time)*30
|
|
sprite("player%d_%d"%[direction_index(game.facing),i%3],game.player+offset,Color(1,1,1,clampf(game.phase_time,0,1)))
|
|
elif game.phase not in ["won","lost","transition"]:
|
|
if game.invulnerable<=0 or int(game.clock*12)%2==0:
|
|
sprite("player%d_%d"%[direction_index(game.facing),int(game.clock*10)%3 if game.moving else 0],game.player)
|
|
for shot in game.shots:
|
|
var tail: Vector2 = shot.position-shot.velocity.normalized()*5
|
|
draw_line(tail.round(),shot.position.round(),Color.WHITE,1)
|
|
for shot in game.enemy_shots:
|
|
if shot.kind=="shell": sprite("shell",shot.position)
|
|
else: draw_rect(Rect2(shot.position.round()-Vector2(1,1),Vector2(3,3)),accent)
|
|
for effect in game.effects:
|
|
if effect.label!="": text(effect.label,effect.position.round()-Vector2(10,8),accent)
|
|
else:
|
|
for i in range(8):
|
|
var p: Vector2 = effect.position+Vector2.from_angle(i*TAU/8)*(1-effect.life/.25)*12
|
|
draw_rect(Rect2(p.round(),Vector2(2,2)),accent)
|
|
if game.phase=="ready": center("GET READY",110,accent)
|
|
if game.phase=="transition":
|
|
for i in range(8):
|
|
var expansion: float = fposmod((1.8-game.phase_time)*150+i*18,150)
|
|
draw_rect(Rect2(Vector2(146,124)-Vector2(expansion,expansion*.7),Vector2(expansion*2,expansion*1.4)),accent,false,1)
|
|
if game.phase=="won": center("YOU WIN!",114,accent)
|
|
if game.phase=="lost" or game.demo: center("GAME OVER",114,accent)
|