learning-gd / index.html
Imfuckinggoodbro's picture
Add 3 files
ead457b verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cosmic GDScript - Learn to Code 2D Shooters</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Roboto+Mono:wght@400;700&display=swap');
:root {
--primary: #6e45e2;
--secondary: #88d3ce;
--accent: #ff9a56;
--dark: #1a1a2e;
--darker: #0d0d1a;
}
body {
font-family: 'Roboto Mono', monospace;
background-color: var(--darker);
color: white;
background-image:
radial-gradient(circle at 10% 20%, rgba(110, 69, 226, 0.1) 0%, transparent 20%),
radial-gradient(circle at 90% 80%, rgba(136, 211, 206, 0.1) 0%, transparent 20%);
}
.retro-border {
border: 2px solid var(--accent);
box-shadow: 0 0 15px rgba(255, 154, 86, 0.3);
}
.pixel-font {
font-family: 'Press Start 2P', cursive;
}
.code-block {
background-color: rgba(26, 26, 46, 0.8);
border-left: 4px solid var(--secondary);
}
.ship-movement {
animation: float 6s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-20px) rotate(2deg); }
}
.star {
position: absolute;
background-color: white;
border-radius: 50%;
animation: twinkle var(--duration) ease-in-out infinite;
}
@keyframes twinkle {
0%, 100% { opacity: 0.2; }
50% { opacity: 1; }
}
.progress-bar {
height: 8px;
background: linear-gradient(90deg, var(--accent) 0%, var(--primary) 100%);
transition: width 0.3s ease;
}
.enemy-pattern {
animation: pattern-move 8s linear infinite;
}
@keyframes pattern-move {
0% { transform: translateX(0) translateY(0); }
25% { transform: translateX(50px) translateY(30px); }
50% { transform: translateX(0) translateY(60px); }
75% { transform: translateX(-50px) translateY(30px); }
100% { transform: translateX(0) translateY(0); }
}
</style>
</head>
<body class="min-h-screen">
<!-- Stars background -->
<div id="stars-container" class="fixed inset-0 overflow-hidden -z-10"></div>
<!-- Header -->
<header class="bg-black bg-opacity-70 backdrop-blur-md sticky top-0 z-50 retro-border border-t-0 border-l-0 border-r-0">
<div class="container mx-auto px-4 py-3 flex justify-between items-center">
<div class="flex items-center space-x-2">
<i class="fas fa-rocket text-2xl text-accent"></i>
<h1 class="text-2xl pixel-font bg-clip-text text-transparent bg-gradient-to-r from-primary to-accent">
COSMIC GDSCRIPT
</h1>
</div>
<nav class="hidden md:flex space-x-6">
<a href="#basics" class="hover:text-secondary transition">Basics</a>
<a href="#movement" class="hover:text-secondary transition">Movement</a>
<a href="#combat" class="hover:text-secondary transition">Combat</a>
<a href="#enemies" class="hover:text-secondary transition">Enemies</a>
<a href="#story" class="hover:text-secondary transition">Story</a>
</nav>
<button class="md:hidden text-xl">
<i class="fas fa-bars"></i>
</button>
</div>
</header>
<!-- Hero Section -->
<section class="relative pt-20 pb-32 px-4">
<div class="container mx-auto flex flex-col md:flex-row items-center">
<div class="md:w-1/2 mb-10 md:mb-0">
<h2 class="text-4xl md:text-5xl font-bold mb-6 pixel-font">
BUILD YOUR OWN <span class="text-accent">SPACE SHOOTER</span>
</h2>
<p class="text-lg mb-8 text-gray-300 max-w-lg">
Learn GDScript from scratch while creating a classic 2D shooter inspired by Gaiares, Thunder Force, and other legendary titles. Perfect for beginners!
</p>
<div class="flex space-x-4">
<a href="#start" class="px-6 py-3 bg-gradient-to-r from-primary to-accent rounded-md font-bold hover:opacity-90 transition">
Start Learning
</a>
<a href="#tutorials" class="px-6 py-3 border border-accent text-accent rounded-md font-bold hover:bg-accent hover:bg-opacity-10 transition">
Watch Tutorials
</a>
</div>
</div>
<div class="md:w-1/2 flex justify-center">
<div class="relative w-64 h-64 ship-movement">
<img src="https://img.icons8.com/color/96/000000/space-shuttle.png" alt="Spaceship" class="w-full h-full">
<div class="absolute -bottom-8 left-1/2 transform -translate-x-1/2 w-32 h-4 bg-accent rounded-full blur-md opacity-70"></div>
</div>
</div>
</div>
</section>
<!-- Progress Tracker -->
<div class="container mx-auto px-4 mb-12">
<div class="bg-black bg-opacity-50 rounded-lg p-4 retro-border">
<h3 class="text-lg font-bold mb-2">YOUR LEARNING PROGRESS</h3>
<div class="w-full bg-gray-800 rounded-full h-2 mb-2">
<div class="progress-bar rounded-full h-2" style="width: 0%"></div>
</div>
<p class="text-sm text-gray-400">Complete sections to unlock the next chapters!</p>
</div>
</div>
<!-- Main Content -->
<main class="container mx-auto px-4 pb-20 space-y-20">
<!-- Basics Section -->
<section id="basics" class="scroll-mt-20">
<div class="bg-black bg-opacity-50 rounded-lg p-6 retro-border">
<div class="flex items-center mb-6">
<div class="w-10 h-10 rounded-full bg-primary flex items-center justify-center mr-4">
<span class="text-xl font-bold">1</span>
</div>
<h2 class="text-2xl font-bold pixel-font">GDScript Basics for Space Games</h2>
</div>
<div class="grid md:grid-cols-2 gap-8">
<div>
<h3 class="text-xl font-semibold mb-4 text-secondary">Why GDScript for Shooters?</h3>
<p class="mb-4 text-gray-300">
GDScript is the perfect language for 2D space shooters because it's:
</p>
<ul class="space-y-2 mb-6">
<li class="flex items-start">
<i class="fas fa-check text-accent mr-2 mt-1"></i>
<span>Easy to learn with Python-like syntax</span>
</li>
<li class="flex items-start">
<i class="fas fa-check text-accent mr-2 mt-1"></i>
<span>Optimized for Godot's 2D engine</span>
</li>
<li class="flex items-start">
<i class="fas fa-check text-accent mr-2 mt-1"></i>
<span>Great for rapid prototyping of game mechanics</span>
</li>
<li class="flex items-start">
<i class="fas fa-check text-accent mr-2 mt-1"></i>
<span>Perfect for handling physics and collisions</span>
</li>
</ul>
<h3 class="text-xl font-semibold mb-4 text-secondary mt-8">Setting Up Your Project</h3>
<p class="mb-4 text-gray-300">
Start with these basic settings for an authentic arcade feel:
</p>
<div class="code-block p-4 rounded mb-6">
<pre class="text-sm text-gray-200">
# Project Settings -> Display -> Window
window/size/width = 640 # Classic arcade width
window/size/height = 720 # Vertical shooter height
window/stretch/mode = "2d" # Pixel perfect scaling
# Physics Settings
physics/2d/default_gravity = 0 # No gravity in space!
physics/2d/default_linear_damp = 0.1 # Small drag for inertia</pre>
</div>
</div>
<div>
<h3 class="text-xl font-semibold mb-4 text-secondary">Your First Spaceship Script</h3>
<p class="mb-4 text-gray-300">
Here's a simple script to get your ship on screen:
</p>
<div class="code-block p-4 rounded mb-4">
<pre class="text-sm text-gray-200">
extends Area2D # We use Area2D for collision detection
# Ship properties
var speed = 300
var velocity = Vector2.ZERO
func _process(delta):
# Get input
var input = Vector2.ZERO
input.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
input.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
# Normalize diagonal movement
if input.length() > 0:
input = input.normalized()
# Update position
velocity = input * speed
position += velocity * delta
# Keep ship on screen
position.x = clamp(position.x, 0, get_viewport_rect().size.x)
position.y = clamp(position.y, 0, get_viewport_rect().size.y)</pre>
</div>
<p class="text-sm text-gray-400">
This gives you basic 8-directional movement with screen boundaries.
</p>
</div>
</div>
<div class="mt-8 bg-black bg-opacity-70 p-4 rounded-lg">
<h4 class="font-semibold text-secondary mb-2">Beginner Tip:</h4>
<p class="text-gray-300">
Use <code class="bg-gray-800 px-1 rounded">Input.get_action_strength()</code> instead of <code class="bg-gray-800 px-1 rounded">Input.is_action_pressed()</code> for smoother analog input support, which works great with gamepads!
</p>
</div>
</div>
</section>
<!-- Movement Section -->
<section id="movement" class="scroll-mt-20">
<div class="bg-black bg-opacity-50 rounded-lg p-6 retro-border">
<div class="flex items-center mb-6">
<div class="w-10 h-10 rounded-full bg-primary flex items-center justify-center mr-4">
<span class="text-xl font-bold">2</span>
</div>
<h2 class="text-2xl font-bold pixel-font">Advanced Movement Systems</h2>
</div>
<div class="grid md:grid-cols-2 gap-8">
<div>
<h3 class="text-xl font-semibold mb-4 text-secondary">Inertia & Momentum</h3>
<p class="mb-4 text-gray-300">
For games like Thunder Force with realistic space physics:
</p>
<div class="code-block p-4 rounded mb-4">
<pre class="text-sm text-gray-200">
extends Area2D
var max_speed = 500
var acceleration = 800
var friction = 0.95
var velocity = Vector2.ZERO
func _process(delta):
var input = Vector2.ZERO
input.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
input.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
if input.length() > 0:
velocity += input.normalized() * acceleration * delta
velocity = velocity.limit_length(max_speed)
else:
velocity *= friction
position += velocity * delta
position = position.clamp(Vector2.ZERO, get_viewport_rect().size)</pre>
</div>
<p class="text-sm text-gray-400">
This gives your ship realistic momentum that gradually slows down when not accelerating.
</p>
</div>
<div>
<h3 class="text-xl font-semibold mb-4 text-secondary">Screen Wrapping</h3>
<p class="mb-4 text-gray-300">
Classic arcade-style screen wrapping (like in Asteroids):
</p>
<div class="code-block p-4 rounded mb-4">
<pre class="text-sm text-gray-200">
func _process(delta):
# ... (previous movement code)
# Screen wrapping
var screen_size = get_viewport_rect().size
if position.x < 0:
position.x = screen_size.x
elif position.x > screen_size.x:
position.x = 0
if position.y < 0:
position.y = screen_size.y
elif position.y > screen_size.y:
position.y = 0</pre>
</div>
<h3 class="text-xl font-semibold mb-4 text-secondary mt-8">Ship Tilting Animation</h3>
<p class="mb-4 text-gray-300">
Add visual feedback when moving:
</p>
<div class="code-block p-4 rounded">
<pre class="text-sm text-gray-200">
func _process(delta):
# ... (movement code)
# Visual tilt based on movement
var tilt_amount = 15 # degrees
var target_rotation = -input.x * deg2rad(tilt_amount)
$Sprite.rotation = lerp($Sprite.rotation, target_rotation, 10 * delta)</pre>
</div>
</div>
</div>
<div class="mt-8 grid md:grid-cols-2 gap-6">
<div class="bg-black bg-opacity-70 p-4 rounded-lg">
<h4 class="font-semibold text-secondary mb-2">Pro Tip: Movement Styles</h4>
<p class="text-gray-300">
Different shooters use different movement systems:
</p>
<ul class="mt-2 space-y-1">
<li><span class="text-accent">Fixed</span>: Always moves at constant speed (Gradius)</li>
<li><span class="text-accent">Inertial</span>: Realistic momentum (Thunder Force)</li>
<li><span class="text-accent">Float</span>: Slow acceleration/deceleration (Gaiares)</li>
</ul>
</div>
<div class="bg-black bg-opacity-70 p-4 rounded-lg">
<h4 class="font-semibold text-secondary mb-2">Exercise:</h4>
<p class="text-gray-300">
Try creating a "speed boost" system where holding a button increases max speed temporarily but makes your hitbox larger.
</p>
</div>
</div>
</div>
</section>
<!-- Combat Section -->
<section id="combat" class="scroll-mt-20">
<div class="bg-black bg-opacity-50 rounded-lg p-6 retro-border">
<div class="flex items-center mb-6">
<div class="w-10 h-10 rounded-full bg-primary flex items-center justify-center mr-4">
<span class="text-xl font-bold">3</span>
</div>
<h2 class="text-2xl font-bold pixel-font">Weapons & Power-Ups</h2>
</div>
<div class="grid md:grid-cols-2 gap-8">
<div>
<h3 class="text-xl font-semibold mb-4 text-secondary">Basic Weapon System</h3>
<p class="mb-4 text-gray-300">
A flexible weapon system that supports multiple shot types:
</p>
<div class="code-block p-4 rounded mb-4">
<pre class="text-sm text-gray-200">
enum WEAPON_TYPES {NORMAL, SPREAD, LASER}
var current_weapon = WEAPON_TYPES.NORMAL
var can_shoot = true
var fire_rate = 0.2
func _input(event):
if event.is_action_pressed("shoot") and can_shoot:
shoot()
can_shoot = false
$FireRateTimer.start(fire_rate)
func shoot():
match current_weapon:
WEAPON_TYPES.NORMAL:
create_bullet($GunPosition.position, Vector2(0, -800))
WEAPON_TYPES.SPREAD:
for i in range(3):
var angle = deg2rad(-15 + i * 15)
create_bullet($GunPosition.position, Vector2(0, -800).rotated(angle))
WEAPON_TYPES.LASER:
$LaserBeam.emitting = true
$LaserTimer.start(0.5)
func create_bullet(pos, velocity):
var bullet = preload("res://Bullet.tscn").instance()
bullet.position = position + pos
bullet.velocity = velocity
get_parent().add_child(bullet)
func _on_FireRateTimer_timeout():
can_shoot = true
func _on_LaserTimer_timeout():
$LaserBeam.emitting = false</pre>
</div>
</div>
<div>
<h3 class="text-xl font-semibold mb-4 text-secondary">Power-Up System</h3>
<p class="mb-4 text-gray-300">
Collectible items that enhance your weapons:
</p>
<div class="code-block p-4 rounded mb-4">
<pre class="text-sm text-gray-200">
# In your ship script
var weapon_level = 0
var max_weapon_level = 3
func _on_Area2D_area_entered(area):
if area.is_in_group("powerups"):
match area.powerup_type:
"weapon_up":
weapon_level = min(weapon_level + 1, max_weapon_level)
update_weapon()
"speed_up":
max_speed += 50
"shield":
$Shield.activate()
area.queue_free()
func update_weapon():
match weapon_level:
0: current_weapon = WEAPON_TYPES.NORMAL
1:
current_weapon = WEAPON_TYPES.SPREAD
fire_rate = 0.15
2:
current_weapon = WEAPON_TYPES.LASER
fire_rate = 0.3</pre>
</div>
<h3 class="text-xl font-semibold mb-4 text-secondary mt-8">Super Cannon (Bomb)</h3>
<p class="mb-4 text-gray-300">
Screen-clearing special attack:
</p>
<div class="code-block p-4 rounded">
<pre class="text-sm text-gray-200">
var bomb_count = 3
func _input(event):
if event.is_action_pressed("bomb") and bomb_count > 0:
activate_bomb()
func activate_bomb():
bomb_count -= 1
$BombAnimation.play("explode")
$BombSound.play()
# Damage all enemies
for enemy in get_tree().get_nodes_in_group("enemies"):
enemy.take_damage(999) # Insta-kill
# Clear bullets
for bullet in get_tree().get_nodes_in_group("enemy_bullets"):
bullet.queue_free()</pre>
</div>
</div>
</div>
<div class="mt-8 bg-black bg-opacity-70 p-4 rounded-lg">
<h4 class="font-semibold text-secondary mb-2">Design Tip:</h4>
<p class="text-gray-300">
Balance your weapons carefully:
</p>
<ul class="mt-2 space-y-1">
<li><span class="text-accent">Normal</span>: Weak but reliable</li>
<li><span class="text-accent">Spread</span>: Good for crowds but weaker individually</li>
<li><span class="text-accent">Laser</span>: Powerful but slow firing rate</li>
</ul>
</div>
</div>
</section>
<!-- Enemies Section -->
<section id="enemies" class="scroll-mt-20">
<div class="bg-black bg-opacity-50 rounded-lg p-6 retro-border">
<div class="flex items-center mb-6">
<div class="w-10 h-10 rounded-full bg-primary flex items-center justify-center mr-4">
<span class="text-xl font-bold">4</span>
</div>
<h2 class="text-2xl font-bold pixel-font">Enemy Patterns & AI</h2>
</div>
<div class="grid md:grid-cols-2 gap-8">
<div>
<h3 class="text-xl font-semibold mb-4 text-secondary">Basic Enemy Movement</h3>
<p class="mb-4 text-gray-300">
Simple but effective enemy patterns:
</p>
<div class="code-block p-4 rounded mb-4">
<pre class="text-sm text-gray-200">
extends Area2D
enum MOVEMENT_PATTERNS {LINEAR, SINUSOIDAL, CIRCULAR, DIVE}
var health = 3
var speed = 150
var pattern = MOVEMENT_PATTERNS.LINEAR
var time = 0
func _process(delta):
time += delta
match pattern:
MOVEMENT_PATTERNS.LINEAR:
position.y += speed * delta
MOVEMENT_PATTERNS.SINUSOIDAL:
position.y += speed * 0.5 * delta
position.x = 200 + sin(time * 2) * 150
MOVEMENT_PATTERNS.CIRCULAR:
position = Vector2(300, 100) + Vector2(cos(time), sin(time)) * 150
MOVEMENT_PATTERNS.DIVE:
if position.y < 200:
position.y += speed * delta
else:
var player = get_tree().get_nodes_in_group("player")[0]
var direction = (player.position - position).normalized()
position += direction * speed * delta</pre>
</div>
<h3 class="text-xl font-semibold mb-4 text-secondary mt-8">Bullet Patterns</h3>
<p class="mb-4 text-gray-300">
Classic shooter bullet patterns:
</p>
<div class="code-block p-4 rounded">
<pre class="text-sm text-gray-200">
func shoot():
var patterns = [
"single",
"three_way",
"circle",
"aimed"
]
call(patterns[randi() % patterns.size()])
func single():
var bullet = create_bullet(Vector2.DOWN * 300)
add_child(bullet)
func three_way():
for i in range(3):
var angle = deg2rad(-15 + i * 15)
var bullet = create_bullet(Vector2.DOWN.rotated(angle) * 250)
add_child(bullet)
func circle():
for i in range(8):
var angle = deg2rad(i * 45)
var bullet = create_bullet(Vector2.DOWN.rotated(angle) * 200)
add_child(bullet)
func aimed():
var player = get_tree().get_nodes_in_group("player")[0]
var direction = (player.position - position).normalized()
var bullet = create_bullet(direction * 350)
add_child(bullet)</pre>
</div>
</div>
<div>
<h3 class="text-xl font-semibold mb-4 text-secondary">Enemy Waves & Spawning</h3>
<p class="mb-4 text-gray-300">
Dynamic wave spawning system:
</p>
<div class="code-block p-4 rounded mb-4">
<pre class="text-sm text-gray-200">
# In your GameController script
var current_wave = 0
var enemies_in_wave = 0
var wave_data = [
{"count": 5, "type": "basic", "pattern": "linear"},
{"count": 8, "type": "basic", "pattern": "sinusoidal"},
{"count": 3, "type": "elite", "pattern": "dive"},
# ... more waves
]
func start_wave(wave_num):
current_wave = wave_num
var wave = wave_data[wave_num]
enemies_in_wave = wave.count
for i in range(wave.count):
var enemy = preload("res://Enemies/" + wave.type + ".tscn").instance()
enemy.pattern = wave.pattern
enemy.position = Vector2(
rand_range(50, 590),
rand_range(-100, -30)
)
add_child(enemy)
yield(get_tree().create_timer(0.5), "timeout")
func _on_Enemy_death():
enemies_in_wave -= 1
if enemies_in_wave <= 0:
yield(get_tree().create_timer(2.0), "timeout")
start_wave(current_wave + 1)</pre>
</div>
<h3 class="text-xl font-semibold mb-4 text-secondary mt-8">Boss Battle Framework</h3>
<p class="mb-4 text-gray-300">
Multi-phase boss structure:
</p>
<div class="code-block p-4 rounded">
<pre class="text-sm text-gray-200">
extends Node2D
var max_health = 1000
var health = max_health
var phase = 1
var attack_patterns = [
"spiral_shot",
"laser_sweep",
"missile_barrage"
]
func _process(delta):
# Phase transitions
if health < max_health * 0.66 and phase == 1:
phase = 2
$AnimationPlayer.play("phase_transition")
elif health < max_health * 0.33 and phase == 2:
phase = 3
$AnimationPlayer.play("final_phase")
# Random attacks
if $AttackCooldown.is_stopped():
var attack = attack_patterns[randi() % attack_patterns.size()]
call(attack)
$AttackCooldown.start(3.0)
func take_damage(amount):
health -= amount
$HealthBar.value = health
if health <= 0:
die()
func die():
$DeathAnimation.play("explode")
yield($DeathAnimation, "animation_finished")
queue_free()
emit_signal("boss_defeated")</pre>
</div>
</div>
</div>
<div class="mt-8 bg-black bg-opacity-70 p-4 rounded-lg">
<h4 class="font-semibold text-secondary mb-2">Pattern Design Tip:</h4>
<p class="text-gray-300">
Good shooter patterns follow these principles:
</p>
<ul class="mt-2 space-y-1">
<li><span class="text-accent">Readability</span>: Players should see and understand the pattern</li>
<li><span class="text-accent">Fairness</span>: Always leave escape routes</li>
<li><span class="text-accent">Variety</span>: Mix different pattern types</li>
<li><span class="text-accent">Rhythm</span>: Attacks should have a predictable tempo</li>
</ul>
</div>
</div>
</section>
<!-- Story Section -->
<section id="story" class="scroll-mt-20">
<div class="bg-black bg-opacity-50 rounded-lg p-6 retro-border">
<div class="flex items-center mb-6">
<div class="w-10 h-10 rounded-full bg-primary flex items-center justify-center mr-4">
<span class="text-xl font-bold">5</span>
</div>
<h2 class="text-2xl font-bold pixel-font">Story & Presentation</h2>
</div>
<div class="grid md:grid-cols-2 gap-8">
<div>
<h3 class="text-xl font-semibold mb-4 text-secondary">Cutscene System</h3>
<p class="mb-4 text-gray-300">
Simple dialogue and scene transitions:
</p>
<div class="code-block p-4 rounded mb-4">
<pre class="text-sm text-gray-200">
# CutscenePlayer.gd
var current_scene = 0
var scenes = [
{"text": "Commander: The Zeta fleet is attacking our colonies!", "duration": 3},
{"text": "We need you to pilot the new X-Wing prototype.", "duration": 3},
{"text": "Good luck out there, pilot!", "duration": 2, "action": "start_game"}
]
func play_scene():
if current_scene >= scenes.size():
return
var scene = scenes[current_scene]
$Label.text = scene.text
$AnimationPlayer.play("show_text")
yield(get_tree().create_timer(scene.duration), "timeout")
if scene.has("action"):
call(scene.action)
current_scene += 1
play_scene()
func start_game():
get_tree().change_scene("res://Game.tscn")</pre>
</div>
<h3 class="text-xl font-semibold mb-4 text-secondary mt-8">Mission Briefing Screen</h3>
<p class="mb-4 text-gray-300">
Display level information:
</p>
<div class="code-block p-4 rounded">
<pre class="text-sm text-gray-200">
# MissionBriefing.gd
func show_mission(level_data):
$Title.text = level_data.title
$Description.text = level_data.description
$Objectives.text = ""
for objective in level_data.objectives:
$Objectives.text += "• " + objective + "\n"
$AnimationPlayer.play("enter")
yield(get_tree().create_timer(5.0), "timeout")
$AnimationPlayer.play("exit")
yield($AnimationPlayer, "animation_finished")
emit_signal("briefing_complete")
# Example level data
var level1 = {
"title": "MISSION 1: BREAKTHROUGH",
"description": "Penetrate the enemy defense line and destroy the carrier.",
"objectives": [
"Destroy all radar installations",
"Defeat the carrier boss",
"Survive for 5 minutes"
]
}</pre>
</div>
</div>
<div>
<h3 class="text-xl font-semibold mb-4 text-secondary">Ending Sequence</h3>
<p class="mb-4 text-gray-300">
Show player results and story conclusion:
</p>
<div class="code-block p-4 rounded mb-4">
<pre class="text-sm text-gray-200">
# EndingHandler.gd
func show_ending(score, rank, secrets_found):
$Score.text = "SCORE: %08d" % score
$Rank.text = "RANK: " + rank
if secrets_found >= 5:
$EndingText.text = ending_data.true_ending
else:
$EndingText.text = ending_data.normal_ending
$AnimationPlayer.play("scroll_text")
yield($AnimationPlayer, "animation_finished")
$ContinuePrompt.show()
var ending_data = {
"normal_ending": "You defeated the Zeta fleet...\nbut their homeworld remains...",
"true_ending": "With the Zeta homeworld destroyed...\npeace returns to the galaxy..."
}</pre>
</div>
<h3 class="text-xl font-semibold mb-4 text-secondary mt-8">Modernizing Classic Storytelling</h3>
<p class="mb-4 text-gray-300">
Techniques to enhance classic shooter narratives:
</p>
<ul class="space-y-2">
<li class="flex items-start">
<i class="fas fa-chevron-right text-accent mr-2 mt-1"></i>
<span><span class="font-semibold">Environmental Storytelling:</span> Show damage to ships, fleeing civilian craft</span>
</li>
<li class="flex items-start">
<i class="fas fa-chevron-right text-accent mr-2 mt-1"></i>
<span><span class="font-semibold">Radio Chatter:</span> Random mission updates during gameplay</span>
</li>
<li class="flex items-start">
<i class="fas fa-chevron-right text-accent mr-2 mt-1"></i>
<span><span class="font-semibold">Unlockable Lore:</span> Collect data logs that expand the universe</span>
</li>
<li class="flex items-start">
<i class="fas fa-chevron-right text-accent mr-2 mt-1"></i>
<span><span class="font-semibold">Branching Paths:</span> Different levels based on mission choices</span>
</li>
</ul>
</div>
</div>
<div class="mt-8 bg-black bg-opacity-70 p-4 rounded-lg">
<h4 class="font-semibold text-secondary mb-2">Exercise:</h4>
<p class="text-gray-300">
Create a simple "codex" system where players can view collected story fragments. Store which entries have been unlocked in a dictionary and save/load it using Godot's ResourceSaver.
</p>
</div>
</div>
</section>
<!-- Final Section -->
<section class="bg-black bg-opacity-50 rounded-lg p-6 retro-border">
<div class="text-center">
<h2 class="text-3xl font-bold pixel-font mb-6">YOUR SPACE ODYSSEY BEGINS!</h2>
<p class="text-xl mb-8 max-w-2xl mx-auto">
You now have all the tools to create an amazing 2D space shooter. Remember that great games are made through iteration - start simple and keep adding polish!
</p>
<div class="flex flex-col md:flex-row justify-center space-y-4 md:space-y-0 md:space-x-6">
<a href="#" class="px-8 py-4 bg-gradient-to-r from-primary to-accent rounded-md font-bold text-lg hover:opacity-90 transition">
Download Project Files
</a>
<a href="#" class="px-8 py-4 border border-accent text-accent rounded-md font-bold text-lg hover:bg-accent hover:bg-opacity-10 transition">
Join Our Community
</a>
</div>
</div>
<div class="mt-12 grid md:grid-cols-3 gap-6">
<div class="bg-black bg-opacity-70 p-6 rounded-lg">
<h3 class="text-xl font-semibold mb-3 text-secondary">Next Steps</h3>
<ul class="space-y-2">
<li class="flex items-start">
<i class="fas fa-rocket text-accent mr-2 mt-1"></i>
<span>Add visual effects (particles, shaders)</span>
</li>
<li class="flex items-start">
<i class="fas fa-music text-accent mr-2 mt-1"></i>
<span>Implement dynamic music</span>
</li>
<li class="flex items-start">
<i class="fas fa-trophy text-accent mr-2 mt-1"></i>
<span>Create an achievement system</span>
</li>
</ul>
</div>
<div class="bg-black bg-opacity-70 p-6 rounded-lg">
<h3 class="text-xl font-semibold mb-3 text-secondary">Resources</h3>
<ul class="space-y-2">
<li class="flex items-start">
<i class="fas fa-book text-accent mr-2 mt-1"></i>
<span>Godot Documentation</span>
</li>
<li class="flex items-start">
<i class="fab fa-youtube text-accent mr-2 mt-1"></i>
<span>GDQuest YouTube Channel</span>
</li>
<li class="flex items-start">
<i class="fas fa-gamepad text-accent mr-2 mt-1"></i>
<span>Shmup Dev Discord</span>
</li>
</ul>
</div>
<div class="bg-black bg-opacity-70 p-6 rounded-lg">
<h3 class="text-xl font-semibold mb-3 text-secondary">Inspiration</h3>
<ul class="space-y-2">
<li class="flex items-start">
<i class="fas fa-star text-accent mr-2 mt-1"></i>
<span>Thunder Force IV</span>
</li>
<li class="flex items-start">
<i class="fas fa-star text-accent mr-2 mt-1"></i>
<span>Gaiares</span>
</li>
<li class="flex items-start">
<i class="fas fa-star text-accent mr-2 mt-1"></i>
<span>R-Type</span>
</li>
</ul>
</div>
</div>
</section>
</main>
<!-- Footer -->
<footer class="bg-black bg-opacity-70 py-8 mt-20 retro-border border-b-0">
<div class="container mx-auto px-4">
<div class="flex flex-col md:flex-row justify-between items-center">
<div class="mb-6 md:mb-0">
<h2 class="text-xl pixel-font bg-clip-text text-transparent bg-gradient-to-r from-primary to-accent">
COSMIC GDSCRIPT
</h2>
<p class="text-sm text-gray-400 mt-1">Learn • Create • Shoot 'em Up</p>
</div>
<div class="flex space-x-6">
<a href="#" class="text-gray-400 hover:text-secondary transition">
<i class="fab fa-twitter text-xl"></i>
</a>
<a href="#" class="text-gray-400 hover:text-secondary transition">
<i class="fab fa-youtube text-xl"></i>
</a>
<a href="#" class="text-gray-400 hover:text-secondary transition">
<i class="fab fa-github text-xl"></i>
</a>
<a href="#" class="text-gray-400 hover:text-secondary transition">
<i class="fab fa-discord text-xl"></i>
</a>
</div>
</div>
<div class="border-t border-gray-800 mt-8 pt-8 text-center text-gray-500 text-sm">
<p>© 2023 Cosmic GDScript. All rights reserved. Made with Godot Engine.</p>
<p class="mt-2">This is a fictional tutorial site created for educational purposes.</p>
</div>
</div>
</footer>
<script>
// Create twinkling stars
document.addEventListener('DOMContentLoaded', function() {
const starsContainer = document.getElementById('stars-container');
const starCount = 100;
for (let i = 0; i < starCount; i++) {
const star = document.createElement('div');
star.classList.add('star');
// Random properties
const size = Math.random() * 3;
const x = Math.random() * 100;
const y = Math.random() * 100;
const duration = 2 + Math.random() * 3;
star.style.width = `${size}px`;
star.style.height = `${size}px`;
star.style.left = `${x}%`;
star.style.top = `${y}%`;
star.style.setProperty('--duration', `${duration}s`);
// Random delay for twinkling
star.style.animationDelay = `${Math.random() * 5}s`;
starsContainer.appendChild(star);
}
// Track scroll progress
const progressBar = document.querySelector('.progress-bar');
const sections = document.querySelectorAll('section');
const totalSections = sections.length;
window.addEventListener('scroll', function() {
let visibleSections = 0;
sections.forEach(section => {
const rect = section.getBoundingClientRect();
if (rect.top < window.innerHeight && rect.bottom > 0) {
visibleSections++;
}
});
const progress = (visibleSections / totalSections) * 100;
progressBar.style.width = `${progress}%`;
});
// Enemy pattern demo
const enemyDemo = document.createElement('div');
enemyDemo.className = 'w-8 h-8 bg-red-500 rounded-full absolute enemy-pattern';
enemyDemo.style.top = '100px';
enemyDemo.style.left = '100px';
document.querySelector('#enemies').appendChild(enemyDemo);
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Imfuckinggoodbro/learning-gd" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>