Skip to content

README.md

ESTONlA edited this page May 25, 2026 · 3 revisions

Texture Replacement Tutorial

This page shows how to make a simple OrcKit .vmz mod that replaces one vanilla texture:

res://enemies/orc_top_down_walking.png

The same idea works for other texture files. The important rule is that your mod package must contain the replacement at the exact same res:// path as the game file.

Final Mod Layout

Create a working folder like this:

OrcWalkingTexture/
  mod.txt
  enemies/
    orc_top_down_walking.png
  scripts/
    texture_mod.gd
  pack_vmz.ps1

When packed, the .vmz root must look like this:

mod.txt
enemies/orc_top_down_walking.png
scripts/texture_mod.gd

Do not package an extra parent folder. This is wrong:

OrcWalkingTexture/mod.txt
OrcWalkingTexture/enemies/orc_top_down_walking.png

That would become:

res://OrcWalkingTexture/enemies/orc_top_down_walking.png

and it will not replace:

res://enemies/orc_top_down_walking.png

Step 1: Create mod.txt

Put this file at:

OrcWalkingTexture/mod.txt
[mod]
id="orc_walking_texture"
name="Orc Walking Texture"
version="1.0.0"
author="YourName"
priority=0

[autoload]
OrcWalkingTexture="res://scripts/texture_mod.gd"

The [autoload] script runs the runtime texture swapper. Keep this section if you want the script-based replacement behavior.

Step 2: Add the Replacement PNG

Put your new PNG here:

OrcWalkingTexture/enemies/orc_top_down_walking.png

The filename must be exactly:

orc_top_down_walking.png

The folder must be exactly:

enemies

Together, that becomes the runtime path:

res://enemies/orc_top_down_walking.png

Keep the replacement image compatible with the original. For animated sprite sheets, use the same frame layout, similar dimensions, and the same transparent background style unless you intentionally want to change those.

Step 3: Add the Runtime Replacement Script

Put this file at:

OrcWalkingTexture/scripts/texture_mod.gd
extends Node

const VANILLA_TEXTURE_PATH := "res://enemies/orc_top_down_walking.png"
const TARGET_FILE := "orc_top_down_walking.png"

var _replacement_texture: Texture2D = null

func _ready() -> void:
	print("[OrcWalkingTexture] loaded")
	_replacement_texture = ResourceLoader.load(
		VANILLA_TEXTURE_PATH,
		"Texture2D",
		ResourceLoader.CACHE_MODE_IGNORE
	) as Texture2D
	if _replacement_texture == null:
		push_warning("[OrcWalkingTexture] replacement texture did not load")
		return
	print("[OrcWalkingTexture] replacement texture mounted")
	get_tree().node_added.connect(_on_node_added)
	await get_tree().process_frame
	_replace_in_tree(get_tree().root)

func _on_node_added(node: Node) -> void:
	call_deferred("_replace_in_tree", node)

func _replace_in_tree(node: Node) -> void:
	if node == null or _replacement_texture == null:
		return
	_replace_on_node(node)
	for child in node.get_children():
		_replace_in_tree(child)

func _replace_on_node(node: Node) -> void:
	if node is Sprite2D:
		var sprite := node as Sprite2D
		if _matches_target(sprite.texture):
			sprite.texture = _make_replacement(sprite.texture)
			print("[OrcWalkingTexture] patched Sprite2D: " + str(sprite.get_path()))

	if node is TextureRect:
		var rect := node as TextureRect
		if _matches_target(rect.texture):
			rect.texture = _make_replacement(rect.texture)
			print("[OrcWalkingTexture] patched TextureRect: " + str(rect.get_path()))

	if node is AnimatedSprite2D:
		_replace_sprite_frames(node as AnimatedSprite2D)

func _replace_sprite_frames(sprite: AnimatedSprite2D) -> void:
	var frames := sprite.sprite_frames
	if frames == null:
		return
	for anim in frames.get_animation_names():
		var count := frames.get_frame_count(anim)
		for i in range(count):
			var old_texture := frames.get_frame_texture(anim, i)
			if not _matches_target(old_texture):
				continue
			var duration := frames.get_frame_duration(anim, i)
			frames.set_frame(anim, i, _make_replacement(old_texture), duration)
			print("[OrcWalkingTexture] patched AnimatedSprite2D frame: " + str(sprite.get_path()))

func _matches_target(texture: Texture2D) -> bool:
	if texture == null:
		return false
	if texture.resource_path == VANILLA_TEXTURE_PATH:
		return true
	if texture.resource_path.get_file() == TARGET_FILE:
		return true
	if texture is AtlasTexture:
		var atlas := texture as AtlasTexture
		if atlas.atlas != null:
			if atlas.atlas.resource_path == VANILLA_TEXTURE_PATH:
				return true
			if atlas.atlas.resource_path.get_file() == TARGET_FILE:
				return true
	return false

func _make_replacement(old_texture: Texture2D) -> Texture2D:
	if old_texture is AtlasTexture:
		var old_atlas := old_texture as AtlasTexture
		var new_atlas := AtlasTexture.new()
		new_atlas.atlas = _replacement_texture
		new_atlas.region = old_atlas.region
		new_atlas.margin = old_atlas.margin
		new_atlas.filter_clip = old_atlas.filter_clip
		return new_atlas
	return _replacement_texture

The .vmz path replacement is still the main thing that makes the mod work. This script is a runtime safety layer: it scans loaded nodes and replaces any Sprite2D, TextureRect, or AnimatedSprite2D frame that still points at the original texture.

Step 4: Pack the .vmz

Put this file at:

OrcWalkingTexture/pack_vmz.ps1
$ErrorActionPreference = "Stop"

$modRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
$outName = "OrcWalkingTexture.vmz"
$outPath = Join-Path $modRoot $outName
$tmpZip = Join-Path $modRoot "OrcWalkingTexture.zip"

if (Test-Path -LiteralPath $outPath) {
    Remove-Item -LiteralPath $outPath -Force
}

if (Test-Path -LiteralPath $tmpZip) {
    Remove-Item -LiteralPath $tmpZip -Force
}

$files = @(
    "mod.txt",
    "enemies/orc_top_down_walking.png",
    "scripts/texture_mod.gd"
)

foreach ($file in $files) {
    $full = Join-Path $modRoot $file
    if (-not (Test-Path -LiteralPath $full)) {
        throw "Missing required file: $file"
    }
}

Compress-Archive `
    -Path `
        (Join-Path $modRoot "mod.txt"), `
        (Join-Path $modRoot "enemies"), `
        (Join-Path $modRoot "scripts") `
    -DestinationPath $tmpZip `
    -Force

Rename-Item -LiteralPath $tmpZip -NewName $outName
Write-Host "Created $outPath"

Run it in PowerShell:

cd "C:\Path\To\OrcWalkingTexture"
.\pack_vmz.ps1

You should get:

OrcWalkingTexture.vmz

.vmz is zip-compatible. OrcKit treats it as a mod archive and mounts it into the game.

Step 5: Install the Mod

Copy the .vmz into the game mods folder:

C:\Program Files (x86)\Steam\steamapps\common\Sir, We Have an Orc Problem Playtest\mods\OrcWalkingTexture.vmz

Launch the game, open the OrcKit Mods menu, and make sure the mod is enabled.

If another mod also replaces the same texture, put this mod later/higher in load order so it wins.

Full Example Tree

OrcWalkingTexture/
  mod.txt
  pack_vmz.ps1
  enemies/
    orc_top_down_walking.png
  scripts/
    texture_mod.gd

Quick Test

After launching the game, check the Godot log for:

[OrcWalkingTexture] loaded
[OrcWalkingTexture] replacement texture mounted

If you see replacement texture did not load, the path inside the .vmz is wrong or the PNG is missing from the archive.

Common Mistakes

Wrong: extra folder inside the .vmz.

OrcWalkingTexture/enemies/orc_top_down_walking.png

Correct inside the .vmz:

enemies/orc_top_down_walking.png

Wrong: filename is close but not exact.

enemies/orc-top-down-walking.png
enemies/orc_top_down_walk.png

Correct:

enemies/orc_top_down_walking.png

Wrong: mod.txt is nested.

OrcWalkingTexture/mod.txt

Correct inside the .vmz:

mod.txt

Quick Checklist

  • Mod is packed as .vmz.
  • mod.txt is at the archive root.
  • File is named exactly orc_top_down_walking.png.
  • File is inside enemies/ at the archive root.
  • Runtime path is exactly res://enemies/orc_top_down_walking.png.
  • Runtime replacement script is at res://scripts/texture_mod.gd.
  • .vmz is placed in the game mods folder.
  • Mod is enabled in OrcKit.
  • Load order is higher/later than other mods replacing the same texture.

Clone this wiki locally