Zelda Game Demo

Talk about anything
User avatar
TheDewd
Posts: 1358
Joined: 19 Dec 2013, 11:16
Location: USA

Zelda Game Demo

13 Mar 2019, 16:17

I had another attempt at creating a tile-based game engine using AutoHotkey.

I used the "Legend of Zelda" NES game tiles and sprites as a template.

I don't want to continue this project anymore, so I thought I'd post the unfinished script here in case anyone else wants to finish it.

I just don't have the time anymore that it's going to require...

Let me know if you decide to work on it. I'd love to see someone else's ideas to make it work better. ;)

Code: Select all

; Auto-Execute =================================================================
#SingleInstance, Force ; Allow only one running instance of script
#Persistent ; Keep the script permanently running until terminated
#NoEnv ; Avoid checking empty variables for environment variables
;#NoTrayIcon ; Disable the tray icon of the script
#KeyHistory, 0 ; Disable history of keystrokes & mouse clicks
SetWorkingDir, % A_ScriptDir ; Set the working directory of the script
SetBatchLines, -1 ; Run script at maximum speed
SetControlDelay, -1 ; Disable delay to occur after modifying a control
SendMode, Input ; The method for sending keystrokes and mouse clicks
ListLines, Off ; Disable history of executed script lines
CoordMode, Mouse, Client ; Mouse coordinates relative to active window client
OnExit("OnUnload") ; Run function when exiting the script

return ; End automatic execution
; ==============================================================================

; Functions ====================================================================
OnLoad() {
	Global ; Assume-global mode
	Static Init := OnLoad() ; Call function

	GuiWidth := 512 ; Gui Width
	GuiHeight := 352 ; Gui Height
	DPI := (A_ScreenDPI / 96) ; Calculate DPI scaling
	BackgroundColor := "FCD8A8"

	BackgroundColors := []
	BackgroundColors[1] := "FCD8A8" ; Map 1
	BackgroundColors[2] := "000000" ; Map 2
	BackgroundColors[3] := "FCD8A8" ; Map 3

	Maps := [] ; Initialize array

	; Start screen
	Maps[1,1] := [1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1]
	Maps[1,2] := [1,1,1,1,10,1,2,0,0,1,1,1,1,1,1,1]
	Maps[1,3] := [1,1,1,2,0,0,0,0,0,1,1,1,1,1,1,1]
	Maps[1,4] := [1,1,2,0,0,0,0,0,0,1,1,1,1,1,1,1]
	Maps[1,5] := [1,2,0,0,0,0,0,0,0,5,1,1,1,1,1,1]
	Maps[1,6] := [0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0]
	Maps[1,7] := [3,4,0,0,0,0,0,0,0,0,0,0,0,0,3,3]
	Maps[1,8] := [1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1]
	Maps[1,9] := [1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1]
	Maps[1,10] := [1,1,3,3,3,3,3,3,3,3,3,3,3,3,1,1]
	Maps[1,11] := [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]

	; Start cave
	Maps[2,1] := [11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11]
	Maps[2,2] := [11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11]
	Maps[2,3] := [11,11,0,0,0,0,0,0,0,0,0,0,0,0,11,11]
	Maps[2,4] := [11,11,0,0,0,0,0,0,0,0,0,0,0,0,11,11]
	Maps[2,5] := [11,11,0,0,0,0,0,0,0,0,0,0,0,0,11,11]
	Maps[2,6] := [11,11,0,0,0,0,0,0,0,0,0,0,0,0,11,11]
	Maps[2,7] := [11,11,0,0,0,0,0,0,0,0,0,0,0,0,11,11]
	Maps[2,8] := [11,11,0,0,0,0,0,0,0,0,0,0,0,0,11,11]
	Maps[2,9] := [11,11,0,0,0,0,0,0,0,0,0,0,0,0,11,11]
	Maps[2,10] := [11,11,12,12,12,12,12,0,0,12,12,12,12,12,11,11]
	Maps[2,11] := [11,11,11,11,11,11,11,6,0,11,11,11,11,11,11,11]

	; North of start
	Maps[3,1] := [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
	Maps[3,2] := [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
	Maps[3,3] := [1,2,0,0,0,0,0,0,0,5,2,0,0,0,5,1]
	Maps[3,4] := [2,0,0,14,0,14,0,0,0,0,0,0,14,0,0,5]
	Maps[3,5] := [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
	Maps[3,6] := [0,0,0,14,0,14,0,0,0,0,0,0,14,0,0,0]
	Maps[3,7] := [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
	Maps[3,8] := [4,0,0,14,0,14,0,0,0,0,0,0,14,0,0,13]
	Maps[3,9] := [1,4,0,0,0,0,0,0,0,13,4,0,0,0,13,1]
	Maps[3,10] := [1,1,3,3,3,3,3,0,0,1,1,3,3,3,1,1]
	Maps[3,11] := [1,1,1,1,1,1,1,6,0,1,1,1,1,1,1,1]

	Actions := []

	Actions[1,2,5] := ["MapSet", 2] ; Cave entrance

	Actions[1,0,8] := ["MapSet", 3] ; Start North (left)
	Actions[1,0,9] := ["MapSet", 3] ; Start North (right)

	Actions[2,12,8] := ["MapSet", 1] ; Cave exit (left)
	Actions[2,12,9] := ["MapSet", 1] ; Cave exit (right)

	Actions[3,12,8] := ["MapSet", 1] ; North > South
	Actions[3,12,9] := ["MapSet", 1] ; North > South

	Zelda := "iVBORw0KGgoAAAANSUhEUgAAAeAAAAAgCAMAAAD9lVunAAADAFBMVEUAAAB7SwAAqACDAAKDywzYmCz82Kj///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy3zVaAAABAHRSTlP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AU/cHJQAAA1lJREFUeAHt2tFyWykQhOGzu0L7/k+clNz50+Q0eEw5vvL0hX0GBqzlq6Ikba5/33Jdz+d1vX6qUv161oiiGbp4ntech531lH/hx+88pmgkk/Pl+mLtVeS/t/xZ3589lnlG8jwRWuX/Ig38PYDZMCk9ln1+zpfEs+samMx/IXnWRP/8yq6D0WqWfT4OrFCRXQdjO+A8T9cN3MBnwNqU5+xj/P4SXGVNcm4PPMYKaIzkhXgMurx+7mE9uKyYez4HDCP1TDxXeR4OwL60GWddAzdwXrcEYLbPPmYTfzd/AqwI6LEIQEk8hlcAzBwB2LuNkbwJnFkDCzfQo96diEn3Z/RKAzdwLmeE0RNg+pin/hyw3zyRFyO06gFpvEW4AGv88dCMKoCFPAZd6mDnA2A9fQJYdQ1shwZu4DxUZw/Mb4iYoZ9kfQ4MkJG5hgEGh590a97AzBlYYUfG2bEGhiuBc9QrnPwweT9PgNOngRt4D+wLI4GV9ZuCGlh7O9eV6+YaIn8E0hM8vqIJvcxrNjN3sKfDzufAVAn6PvDyGg6f+wfWJG3gBuZpB3wHIvSTFdx8mbND9nkEAMEqqtbA+68ioaeLHehQ/gYwTwEcdQLfzzOB6WtgpYGTIrZl4+BlltRvsbQT1Yz8HrBQHXEzJ7jsYJ6Ax3o6cqW6a+A1ZT7d6gDmv18/nXwb6r4GbuCvA849s46rPq4ff0yqgZmHEECoFPWojx2Yma/4rwdmDWf4cWBOqoEbmK8q+VPa3gyM18D0HwAnMSmB4VVgNDCgmmEEaHcBrNyBP/5VZXAHMFUAx4nk+dTADdzAPmJV3rQCPiHN+hlZAXPpKiABDDXEcOYVzS7mvAPDy47nwKpOPibNtHleSS8jpYEbODfdAa958+ONnpWoS2BCJ19DZBIYfDGqA2D/zn8wQD/8J8D5FUcN7Pg8zFYBe8bADdzALFwDV//IU6HDL2xV37t30RozmhYEAlMGdoA9U6wjB8CJmJzOBjgZI1rPh6gGbuBE0vKL1AxxuecLpob7FFhU/OY5k7ODjHl9se4IOAkTOyuN5Nm7yv954xny3YEbWCjaRhcyG1cAygxpYI+49qWvqtpdnSvEmjfnY7RYmcA1Mmiq8hLPy3sNnKeVQeebAzfwT9WcZEukdvhrAAAAAElFTkSuQmCC"

	; GDI+ Startup
	hGdip := DllCall("Kernel32.dll\LoadLibrary", "Str", "Gdiplus.dll") ; Load module
	VarSetCapacity(GdiplusStartupInput, (A_PtrSize = 8 ? 24 : 16), 0) ; GdiplusStartupInput structure
	NumPut(1, GdiplusStartupInput, 0, "UInt") ; GdiplusVersion
	DllCall("Gdiplus.dll\GdiplusStartup", "PtrP", pToken, "Ptr", &GdiplusStartupInput, "Ptr", 0) ; Initialize GDI+

 	; Create a Bitmap object
	DllCall("Gdiplus.dll\GdipCreateBitmapFromScan0", "Int", (GuiWidth * DPI), "Int", (GuiHeight * DPI), "Int", 0, "Int", 0x26200A, "Ptr", 0, "PtrP", pBackgroundImage)

	; Create a Graphics object associated with pBackgroundImage
	DllCall("Gdiplus.dll\GdipGetImageGraphicsContext", "Ptr", pBackgroundImage, "PtrP", pBackgroundGraphics)

	; Set the rendering quality of the pBackgroundGraphics Graphics object
	DllCall("Gdiplus.dll\GdipSetSmoothingMode", "Ptr", pBackgroundGraphics, "Int", 3) ; None

	; Set the interpolation mode of the pBackgroundGraphics Graphics object
	DllCall("Gdiplus.dll\GdipSetInterpolationMode", "Ptr", pBackgroundGraphics, "Int", 5) ; NearestNeighbor

	; Create a SolidBrush object for the background color of the game
	DllCall("Gdiplus.dll\GdipCreateSolidFill", "UInt", "0xFF" BackgroundColor, "PtrP", pBackgroundBrush)
}

OnUnload(ExitReason, ExitCode) {
	Global ; Assume-global mode

	; Release resources used by the Image objects
	DllCall("Gdiplus.dll\GdipDisposeImage", "Ptr", pTheme)
	DllCall("Gdiplus.dll\GdipDisposeImage", "Ptr", pBackgroundImage)

	; Delete Graphics objects
	DllCall("Gdiplus.dll\GdipDeleteGraphics", "Ptr", pBackgroundGraphics)
	DllCall("Gdiplus.dll\GdipDeleteGraphics", "Ptr", pControlGraphics)

	; Delete Brush objects
	DllCall("Gdiplus.dll\GdipDeleteBrush", "Ptr", pBackgroundBrush)

	; Clean up resources used by GDI+
	DllCall("Gdiplus.dll\GdiplusShutdown", "Ptr", pToken)

	; Free module from memory
	DllCall("Kernel32.dll\FreeLibrary", "Ptr", hGdip)
}

GuiCreate() {
	Global ; Assume-global mode
	Static Init := GuiCreate() ; Call function

	Gui, +HWNDhGame -Resize
	Gui, Margin, 0, 0
	Gui, Color, FFFFFF

	Gui, Add, Edit, x0 y0 w0 h0 0x800 vCatch ; Catch keypresses to avoid dinging

	Gui, Add, Picture, % "x" 0 " y" 0 " w" (GuiWidth * DPI) " h" (GuiHeight * DPI) " vGameImage HwndhBackground 0xE"

	; Get pointer to control graphics image
	DllCall("Gdiplus.dll\GdipCreateFromHWND", "Ptr", hBackground, "PtrP", pControlGraphics)

	SubclassControl(hBackground, "BgSubclassProc") ; Subclass static image

	pTheme := GdipCreateFromBase64(Zelda)

	MapSet(1)

	Gui, Show, w%GuiWidth% h%GuiHeight%, Example
}

MapSet(MapID) {
	Global ; Assume-global mode

	BackgroundLoad(MapID)
	MapNo := MapID
}

GuiClose(GuiHwnd) {
	Global ; Assume-global mode

	ExitApp ; Terminate the script unconditionally
}

WM_KEYDOWN(wParam, lParam, Msg, Hwnd) {
	Global ; Assume-global mode
	Static Init := OnMessage(0x0100, "WM_KEYDOWN") ; Call function
	KF_REPEAT := (lParam & 0x40000000)

	VK := Format("{:x}", wParam) ; Get virtual-key code

	GuiControl, Focus, Catch

	If (VK = "26" || VK = "57") { ; UP ARROW/W
		Move(0, -1)
	} Else If (VK = "25" || VK = "41") { ; LEFT ARROW/A
		Move(-1, 0)
	} Else If (VK = "28" || VK = "53") { ; DOWN ARROW/S
		Move(0, 1)
	} Else If (VK = "27" || VK = "44") { ; RIGHT ARROW/D
		Move(1, 0)
	}
}

Move(X, Y) {
	Global ; Assume-global mode

	Update := []

	PlayerGetPos(BDRow, BDCol, Char)

	NewX := (X < 0 ? BDCol - 1 : X > 0 ? BDCol + 1 : BDCol)
	NewY := (Y < 0 ? BDRow - 1 : Y > 0 ? BDRow + 1 : BDRow)
	NewChar := (X < 0 ? 7 : X > 0 ? 9 : Y < 0 ? 6 : 8)

	NextChar := Maps[MapNo, NewY, NewX]

	DoAction(Actions[MapNo, NewY, NewX])

	If (NewX > 16 || NewX < 1)
	|| (NewY > 11 || NewY < 1)
	|| (NextChar <> 0) {
		return
	}

	Maps[MapNo, NewY, NewX] := NewChar ; Move player
	Update.Push({Row: NewY, Col: NewX, Char: NewChar}) ; Update screen

	Maps[MapNo, BDRow, BDCol] := 0 ; Clear previous position
	Update.Push({Row: BDRow, Col: BDCol, Char: 0}) ; Update screen

	BackgroundUpdate(Update)
}

DoAction(Action) {
	Global ; Assume-global mode

	PrevMapNo := MapNo

	If (Action = "") {
		return
	}

	ActionFunc := Func(Action[1])
	ActionFunc.Call(Action[2])

	PlayerGetPos(BDRow, BDCol, Char)

	Update.Push({Row: BDRow, Col: BDCol, Char: NewChar})

	BackgroundUpdate(Update)

}

PlayerGetPos(ByRef Row, ByRef Col, ByRef Val) {
	Global ; Assume-global mode

	For Row In Maps[MapNo] {
		For Col, Val In Maps[MapNo, Row] {
			If (RegExMatch(Val, "(6|7|8|9)")) {
				return Row, Col, Val
			}
		}
	}
}

BackgroundLoad(MapNo) {
	Global ; Assume-global mode

	; Clear graphic to specific color
	DllCall("Gdiplus.dll\GdipGraphicsClear", "Ptr", pBackgroundGraphics, "UInt", "0xFF" BackgroundColors[MapNo])

	; Generate image using tileset coordinates from map data
	For Row In Maps[MapNo] {
		For Col, Val In Maps[MapNo, Row] {
			DrawTile(Val, Col, Row)
		}
	}

	; Redraw background image
	GuiControl, +Redraw, % hBackground
}

BackgroundUpdate(arrUpdate) {
	Global ; Assume-global mode

	For K, V In arrUpdate {
		DllCall("Gdiplus.dll\GdipCreateSolidFill", "UInt", "0xFF" BackgroundColors[MapNo], "PtrP", pBackgroundBrush)

		DllCall("Gdiplus.dll\GdipFillRectangle", "Ptr", pBackgroundGraphics, "Ptr", pBackgroundBrush, "Float", ((V.Col - 1) * 32) * DPI, "Float", ((V.Row - 1) * 32) * DPI, "Float", (32 * DPI), "Float", (32 * DPI))

		DrawTile(V.Char, V.Col, V.Row)
	}

	; Redraw background image
	GuiControl, +Redraw, % hBackground
}

DrawTile(TileChar, X, Y) {
	Global ; Assume-global mode

	DllCall("Gdiplus.dll\GdipDrawImageRectRect", "Ptr", pBackgroundGraphics, "Ptr", pTheme, "Float", ((32 * X) - 32) * DPI, "Float", ((32 * Y) - 32) * DPI, "Float", 32 * DPI, "Float", 32 * DPI, "Float", (TileChar - 1) * 32, "Float", 0, "Float", 32, "Float", 32, "Int", 2, "Ptr", 0, "Ptr", 0, "Ptr", 0)
}

GdipCreateFromBase64(B64) {
	VarSetCapacity(B64Len, 0)
	DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", &B64, "UInt", StrLen(B64), "UInt", 0x01, "Ptr", 0, "UIntP", B64Len, "Ptr", 0, "Ptr", 0)
	VarSetCapacity(B64Dec, B64Len, 0) ; pbBinary size
	DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", &B64, "UInt", StrLen(B64), "UInt", 0x01, "Ptr", &B64Dec, "UIntP", B64Len, "Ptr", 0, "Ptr", 0)
	pStream := DllCall("Shlwapi.dll\SHCreateMemStream", "Ptr", &B64Dec, "UInt", B64Len, "UPtr")
	VarSetCapacity(pBitmap, 0)
	DllCall("Gdiplus.dll\GdipCreateBitmapFromStreamICM", "Ptr", pStream, "PtrP", pBitmap)
	ObjRelease(pStream)
	return pBitmap
}

SubclassControl(HCTL, FuncName, Data := 0) {
	Static ControlCB := []

	If (Controls.HasKey(HCTL)) {
		DllCall("Comctl32.dll\RemoveWindowSubclass", "Ptr", HCTL, "Ptr", ControlCB[HCTL], "Ptr", HCTL)
		DllCall("Kernel32.dll\GlobalFree", "Ptr", Controls[HCTL], "Ptr")
		Controls.Delete(HCTL)

		If (FuncName = "") {
			return true
		}
   }

	If (!DllCall("User32.dll\IsWindow", "Ptr", HCTL, "UInt"))
	|| (!IsFunc(FuncName) || (Func(FuncName).MaxParams <> 6))
	|| (!CB := RegisterCallback(FuncName,, 6)) {
		return false
	}

	If (!DllCall("Comctl32.dll\SetWindowSubclass", "Ptr", HCTL, "Ptr", CB, "Ptr", HCTL, "Ptr", Data)) {
		return (DllCall("Kernel32.dll\GlobalFree", "Ptr", CB, "Ptr") & 0)
	}

	return (ControlCB[HCTL] := CB)
}

BgSubclassProc(hWnd, uMsg, wParam, lParam, uIdSubclass, dwRefData) {
	Global pControlGraphics, pBackgroundImage

	If (uMsg = 0x0014) { ; WM_ERASEBKGND
		return 1 ; Do nothing
	}

	If (uMsg = 0x000F) { ; WM_PAINT
		; Draw the background image
		VarSetCapacity(PAINTSTRUCT, 60 + A_PtrSize, 0)
		DllCall("User32.dll\BeginPaint", "Ptr", hWnd, "Ptr", &PAINTSTRUCT, "UPtr")
		DllCall("Gdiplus.dll\GdipDrawImage", "Ptr", pControlGraphics, "Ptr", pBackgroundImage, "Float", 0, "Float", 0)
		DllCall("User32.dll\EndPaint", "Ptr", hWnd, "Ptr", &PAINTSTRUCT, "UPtr")
		return 0
	}

	If (uMsg = 0x0002) { ; WM_DESTROY
		SubclassControl(hWnd, "") ; Remove the Subclass
	}

	; Pass all other messages to DefSubclassProc
	return DllCall("Comctl32.dll\DefSubclassProc", "Ptr", hWnd, "UInt", uMsg, "Ptr", wParam, "Ptr", lParam, "Ptr")
}
; ==============================================================================
Inspiration came from Javascript/Canvas Zelda:
https://www.airpair.com/javascript/posts/the-legend-of-canvas
http://anonymous-function.com/zelda-canvas/
Image Bulldozer - Sokoban inspired game from 1994 recreated in AutoHotkey.

Return to “Offtopic”

Who is online

Users browsing this forum: No registered users and 4 guests