Coin Dash Pt. 1
CMSC 197 GDD Activity 2
>>Table of Contents
Session Objectives
- Understand Area2D-based collision detection
- Implement input-driven movement with proper delta timing
- Build reusable, testable game objects through scene composition
- Use signals for decoupled communication
Project Setup
1. Create New Project
- Open Godot, click New Project
- Name:
CoinDash - Create project folder
- Click Create & Edit
2. Configure Window Settings
Project, Project Settings, Display, Window:
Viewport Width: 480
Viewport Height: 720
Stretch/Mode: canvas_items
Stretch/Aspect: keep
Size/Resizable: OFF
3. Import Assets
- Download
coin_dash_assets.zip - Unzip into project folder
- Verify
res://assets/contains player, coin, audio folders
Common Pitfalls
Window too small? Zoom viewport using View, Zoom In (Ctrl+Plus)
Assets not showing? Check FileSystem panel, right-click, Reimport
Part 1: Player Scene Architecture
Design Decision: Why Area2D?
We use Area2D as the root because the player needs:
- Collision detection (not physics response)
- Signal-based communication when overlapping coins/obstacles
- Manual movement control (not rigid body simulation)
Scene Tree Structure
Player (Area2D)
|-- AnimatedSprite2D
`-- CollisionShape2D
Step-by-Step Build
1. Create Root Node
- Click Add/Create New Node (Ctrl+A)
- Search:
Area2D - Rename to
Player - Save scene (Ctrl+S):
player.tscn
2. Lock Children (CRITICAL)
- Select
Playernode - Click icon next to lock: Group Selected Node(s)
- Tooltip: Make selected node's children not selectable
- Icon appears next to Player name
Common Pitfalls
Forgetting to group children causes:
- Accidentally moving/scaling child nodes
- Misaligned collision shapes
- Hard-to-debug position offsets
Always group immediately after creating root!
3. Add AnimatedSprite2D
- Select Player, Add Child Node,
AnimatedSprite2D - Inspector, Frames: empty, New SpriteFrames
- Click the
SpriteFramestext to open panel at bottom
4. Configure Animations
In SpriteFrames panel (bottom):
- Rename default animation to
run - Click Add Animation, create
idle - Click Add Animation, create
hurt - For each animation:
- Navigate to
res://assets/player/in FileSystem - Drag corresponding images into animation frames
- Set Speed: 8 FPS
- Navigate to
- Select
idle, click Autoplay on Load (play icon)
5. Scale Sprite
- Select
AnimatedSprite2D - Inspector, Transform, Scale:
(2, 2)
6. Add Collision Shape
- Select Player, Add Child,
CollisionShape2D - Inspector, Shape: empty, New RectangleShape2D
- Drag orange handles to cover sprite
- Pro tip: Hold Alt while dragging for symmetric sizing
7. Adjust Sprite Offset
- Select
AnimatedSprite2D - Inspector, Offset:
(0, -5) - Reason: Centers collision shape on sprite's visual mass
Part 2: Player Script
Attach Script
- Select
Playernode - Click Attach Script icon (scroll icon)
- Accept default path:
res://player.gd - Uncheck Template, Click Create
Variable Declarations
Architecture Note: Strong Typing
GDScript supports optional static typing. This course requires type annotations for:
- All member variables
- All function parameters and return types
Benefits: Earlier error detection, better autocomplete, clearer intent.
extends Area2D
# Custom signals for decoupled communication
signal pickup
signal hurt
# Exported variables appear in Inspector
@export var speed: int = 350
# Internal state
var velocity: Vector2 = Vector2.ZERO
var screensize: Vector2 = Vector2(480, 720)
Key Concepts:
@export: Makes variable Inspector-editablesignal: Declares events this node can emitVector2.ZERO: Shorthand forVector2(0, 0)
Movement Implementation
func _process(delta: float) -> void:
# Get normalized input vector (-1 to 1 on each axis)
velocity = Input.get_vector("ui_left", "ui_right",
"ui_up", "ui_down")
# Update position with delta-independent movement
position += velocity * speed * delta
# Clamp to screen boundaries
position.x = clamp(position.x, 0, screensize.x)
position.y = clamp(position.y, 0, screensize.y)
# Animation state machine
if velocity.length() > 0:
$AnimatedSprite2D.animation = "run"
else:
$AnimatedSprite2D.animation = "idle"
# Flip sprite based on horizontal direction
if velocity.x != 0:
$AnimatedSprite2D.flip_h = velocity.x < 0
Design Pattern: Delta-Independent Movement
Why multiply by delta?
- Godot targets 60 FPS (delta approximately 0.016s)
- Frame rate fluctuates due to CPU load
- speed times delta = pixels per second (frame-independent)
- Without delta: movement speed tied to frame rate
Formula: distance = velocity times time
Game State Control
func start() -> void:
"""Reset player for new game."""
set_process(true)
position = screensize / 2
$AnimatedSprite2D.animation = "idle"
func die() -> void:
"""Handle player death."""
$AnimatedSprite2D.animation = "hurt"
set_process(false)
Architecture note: set_process(false) disables _process() calls. Main scene controls lifecycle.
Collision Detection Setup
func _on_area_entered(area: Area2D) -> void:
if area.is_in_group("coins"):
area.pickup()
pickup.emit()
if area.is_in_group("obstacles"):
hurt.emit()
die()
Connecting the signal:
- Select
Playernode - Click Node tab (next to Inspector)
- Find
area_enteredsignal - Double-click, click Connect
- Godot creates
_on_area_entered()function
Design Pattern: Groups for Type Checking
Instead of checking node types directly:
- Use
is_in_group()for flexible categorization - Allows multiple objects in same group
- Decouples player from specific node types
- Easy to add new collectible/obstacle types
Testing the Player
Run the Scene
- Press F6 or click Run Current Scene
- Use arrow keys to move
- Verify sprite flips correctly
- Check animations switch (run/idle)
Common Pitfalls
Player not moving?
- Check
speedvalue in Inspector - Verify Input Map has ui_left/right/up/down actions
- Confirm
_process()has correct spelling
Player leaving screen?
- Verify
screensizematches window settings - Check clamp() lines for typos
Animation not playing?
- Confirm Autoplay on Load is set for idle
- Check animation names match code exactly (case-sensitive)
Complete Player Script
extends Area2D
signal pickup
signal hurt
@export var speed: int = 350
var velocity: Vector2 = Vector2.ZERO
var screensize: Vector2 = Vector2(480, 720)
func _process(delta: float) -> void:
velocity = Input.get_vector("ui_left", "ui_right",
"ui_up", "ui_down")
position += velocity * speed * delta
position.x = clamp(position.x, 0, screensize.x)
position.y = clamp(position.y, 0, screensize.y)
if velocity.length() > 0:
$AnimatedSprite2D.animation = "run"
else:
$AnimatedSprite2D.animation = "idle"
if velocity.x != 0:
$AnimatedSprite2D.flip_h = velocity.x < 0
func start() -> void:
set_process(true)
position = screensize / 2
$AnimatedSprite2D.animation = "idle"
func die() -> void:
$AnimatedSprite2D.animation = "hurt"
set_process(false)
func _on_area_entered(area: Area2D) -> void:
if area.is_in_group("coins"):
area.pickup()
pickup.emit()
if area.is_in_group("obstacles"):
hurt.emit()
die()
Architecture Review
What We Built
- Self-contained player object (scene composition)
- Signal-based communication (decoupling)
- State control via lifecycle methods (start/die)
- Group-based collision detection (flexibility)
Key Takeaways
- Modularity: Player scene works independently, testable in isolation
- Separation of Concerns: Movement logic separate from collision handling
- Extensibility: Easy to add new animations or collision types
- Type Safety: Strong typing catches errors at edit-time
Next Session Preview
- Build Coin scene with pickup behavior
- Create Main scene with spawning system
- Implement HUD with signal-driven updates
- Complete game loop architecture
This activity is to be accomplished in class.