You have an idea for a pixel RPG. You've been thinking about it for months โ the world, the characters, the combat system. But you haven't started because you can't draw, you can't afford to hire an artist, and placeholder boxes just don't feel inspiring.
Good news: you don't need to draw. Let's build a playable RPG prototype this afternoon using free pixel art assets and Godot 4.
What We're Building
By the end of this guide, you'll have:
- A player character that moves around a tile-based map
- Enemies that chase the player
- A simple combat system (bump-to-attack)
- Collectible items
- A basic UI showing health and score
Not a finished game โ a prototype that proves your concept works and feels fun to play.
Step 1: Gather Your Assets
Before writing a single line of code, let's collect the sprites we need. For this prototype, grab these free assets:
Player Character Pick a hero that matches your game's vibe. A knight for classic fantasy, a viking for Norse mythology, or a paladin for a holy warrior quest.
Enemies Every RPG needs something to fight. A skeleton warrior makes a perfect first enemy โ recognizable, classic, and satisfying to defeat. Add a bat for variety.
Items & Loot Reward your player with loot: treasure chests, gold coins, and mushroom power-ups for health restoration.
Weapons Give your hero options: a steel sword for balanced combat, a battle axe for heavy hits, or a katana for speed.
Effects Combat needs feedback. A fireball for magic attacks adds visual punch to your battles.
Download these as PNGs and organize them in your Godot project:
res://
โโโ sprites/
โ โโโ player/
โ โโโ enemies/
โ โโโ items/
โ โโโ effects/
โโโ scenes/
โโโ scripts/
Step 2: Project Setup in Godot 4
Create a new Godot 4 project. Set these essential settings:
Project Settings:
- Display โ Window โ Size: 320ร180 (native resolution)
- Display โ Window โ Stretch โ Mode: canvas_items
- Display โ Window โ Stretch โ Aspect: keep
- Rendering โ Textures โ Default Texture Filter: Nearest
That last setting is crucial โ it keeps pixel art crisp instead of blurry when scaled up.
Step 3: The Player Character
Create a new scene with this structure:
CharacterBody2D (Player)
โโโ Sprite2D
โโโ CollisionShape2D
โโโ AnimationPlayer (optional)
Attach this script to the Player node:
extends CharacterBody2D
@export var speed: float = 80.0
var health: int = 100
var score: int = 0
func _physics_process(delta: float) -> void:
var direction := Vector2.ZERO
direction.x = Input.get_axis("ui_left", "ui_right")
direction.y = Input.get_axis("ui_up", "ui_down")
if direction.length() > 0:
direction = direction.normalized()
velocity = direction * speed
move_and_slide()
This gives you 8-directional movement. Simple, responsive, and good enough for a prototype.
Step 4: Simple Enemy AI
Create an enemy scene with the same structure as the player. The AI is deliberately simple โ chase the player when nearby:
extends CharacterBody2D
@export var speed: float = 40.0
@export var chase_range: float = 100.0
@export var damage: int = 10
var health: int = 30
func _physics_process(delta: float) -> void:
var player = get_tree().get_first_node_in_group("player")
if not player:
return
var distance = global_position.distance_to(player.global_position)
if distance < chase_range:
var direction = (player.global_position - global_position).normalized()
velocity = direction * speed
else:
velocity = Vector2.ZERO
move_and_slide()
Remember to add your player to the "player" group in the editor.
Step 5: Bump Combat
The simplest combat system: when player and enemy collide, both take damage. Add an Area2D with a collision shape to both player and enemy, then handle the overlap:
# In enemy script, add:
func _on_hitbox_body_entered(body: Node2D) -> void:
if body.is_in_group("player"):
body.take_damage(damage)
take_damage(50) # Player hits back harder
func take_damage(amount: int) -> void:
health -= amount
# Flash white for feedback
modulate = Color.RED
await get_tree().create_timer(0.1).timeout
modulate = Color.WHITE
if health <= 0:
queue_free()
Step 6: Collectible Items
Create an item scene โ simpler than enemies since items don't move:
extends Area2D
@export var item_type: String = "coin"
@export var value: int = 10
func _on_body_entered(body: Node2D) -> void:
if body.is_in_group("player"):
match item_type:
"coin":
body.score += value
"health":
body.health = min(body.health + value, 100)
queue_free()
Scatter coins and health pickups around your map. The treasure chest could drop multiple coins when the player walks over it.
Step 7: Basic UI
Add a CanvasLayer with a simple HUD:
extends CanvasLayer
@onready var health_label: Label = $HealthLabel
@onready var score_label: Label = $ScoreLabel
func _process(delta: float) -> void:
var player = get_tree().get_first_node_in_group("player")
if player:
health_label.text = "HP: %d" % player.health
score_label.text = "Score: %d" % player.score
Use a pixel font (Godot's built-in bitmap font works) to keep the aesthetic consistent.
Step 8: Build a Test Map
Create a TileMap with a simple layout. You don't need elaborate level design for a prototype โ a few rooms connected by corridors, with enemies and items placed by hand.
Place 3-5 enemies, 10+ coins, and 2-3 health pickups. That's enough to test whether your core loop (explore โ fight โ loot โ repeat) feels fun.
The Prototype Mindset
Here's the most important lesson: stop when it's fun. Don't add inventory systems, dialogue, saving, or menus until you've confirmed the basic loop feels good. If moving around and fighting skeletons with a pixel knight feels satisfying, you have a game worth building. If it doesn't, iterate on the core mechanics before adding complexity.
The assets you downloaded are CC0 licensed โ you can use them in your finished game too, not just the prototype. When you're ready to polish, you can replace some sprites with custom art while keeping others. Mix and match.
Next Steps
Once your prototype is fun:
- Add more enemy types with different behaviors
- Implement a proper health bar with UI icons
- Add screen shake and particles for combat feedback
- Design real levels with intentional pacing
- Add sound effects (freesound.org has CC0 audio)
The gap between "prototype" and "game" is mostly polish and content. The hard part โ proving your idea is fun โ you just finished in an afternoon.
Now go build something.