Spaces:
Running
Running
| <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> |