We're archiving the forums and going read only! You'll be able to see old threads, but new topics and replies have been disabled.

Visit the Game Jolt community for new questions and conversations.


Hello. I'm NukeOTron. I make video games, and I'm close to realizing how to simulate SNES-style HDMA effects.

...now, you might ask "Whaddaya mean, SNES-style HDMA effects?" Well, for that, I recommend you go to Retro Game Mechanics Explained's YouTube channel for the basic idea, since he can do it better. However, I can show you a few things it can do.

hdmahwarp.png
hdmavwarp.png

Now, imagine those, but actually moving. With HDMA effects, you can manipulate an image, one horizontal line at a time, creating interesting distortions like these.

However, there is one thing that eludes me, and that's the Mode 7/HDMA perspective effect I'm trying to go for. I'm an artist, not a mathematician.

As such, I'm gonna have to reveal some code to you, the general public. It's for Game Maker Studio 1, so it could be compatible with GM Studio 2.

Here's the Create event:

		
			///Create the HDMA stuffs
HDMALine= surface_create(512,2) //there's a reason why they're powers of 2
Display = surface_create(512,256)
Script= RBTMode7

DrawX=x
DrawY=y
HLine=0
Scale=0

Shade=c_white

ScaleStart=1
Zoom=1

DrawHigh=0
		
	

Now, those two surfaces are important for drawing the picture properly. HDMALine is for the picture on the specific scanline, and Display is where HDMALine will be placed 224 times (the vertical size of my window) as a certain variable increases. Script is for the HDMA effect I have planned, and will be repeated 224 times. DrawX and DrawY are where my object will be drawn. HLine is the number that will increase for every scanline. Scale is the size things will be drawn. Shade will change with Scale, when things are farther away. Zoom determines how big the picture will be without the perspective, and ScaleStart should change the perspective. DrawHigh helps determine the cutoff point for our Mode 7 effects.

Now, here's the Draw event, the heart of HDMA-style drawing:

		
			///Draw the HDMA stuffs
if surface_exists(HDMALine)=false HDMALine= surface_create(512,2)
if surface_exists(Display)=false Display= surface_create(512,256)
if image_angle<0 image_angle+=360
if image_angle>360 image_angle-=360
//Mode 7 initialization
Scale=ScaleStart

//Back to regularly scheduled HDMA drawing stuff
HLine=0
surface_set_target(Display)
 draw_clear_alpha(c_black,0)
surface_reset_target()

repeat(224){
surface_set_target(HDMALine)
 script_execute(Script)
surface_reset_target()

surface_set_target(Display)
draw_surface_part(HDMALine,0,0,512,1,0,HLine+DrawHigh)
surface_reset_target()
HLine+=1
}

draw_surface(Display,view_xview,view_yview)
		
	

First, it double-checks if those surfaces are still there, due to how GM Studio works. The Mode 7 initialization bit is to establish variables that would later be used in the script. (For example, you could replace the initialization bit with something that makes the screen constantly wiggle, or add a number to DrawX.) We set HLine to 0 every time because it will add up 224 times in the repeat section, and we clear the Display surface so we don't accidentally redraw on it every frame.

At the repeat(224) part, it does the drawing on the scanline, and repeats that 224 times, because that little Script that it's executing is going to be drawing something slightly different for each line. Then it draws HDMALine onto the Display surface. (also, HDMALine's only using draw_surface_part() because you only need the top half. Apparently, HTML5 doesn't like any surface size that isn't a power of 2.) ...and, because it's measuring HLine 224 times, HLine goes up 1.

Then we just draw the surface, and rebalance the colors for the next thing that needs to draw, whatever that is.

Now, here's where things get messy: my script for the Mode 7 Scale effect.

		
			draw_clear_alpha(c_black,0)
///The Mode 7 scaling stuff
Scale= Zoom*(ScaleStart*(HLine/(view_hview/2)))

if Scale<abs(1) Shade=make_color_rgb(abs(Scale)*255,abs(Scale)*255,abs(Scale)*255)
if Scale>=abs(1) Shade=c_white
draw_sprite_ext(RBTTitleSpr,-1,(view_wview/2)+((DrawX-(view_wview/2))*Scale),(view_hview/2)+((DrawY-(view_hview/2))*Scale)-HLine,Scale,Scale,image_angle,Shade,1)
		
	

Okay, so as most Game Maker programmers know, Game Maker already can do sprite scaling and sprite rotation. What it can't do without a bit of math is do it in perspective. I am 100% confident in what's going on in draw_sprite_ext(), as I've made so many games that have used a variation of that code, which serves to make things disappear into the middle of the screen if Scale is 0. (also, why the y variable is being subtracted by HLine is to get the picture drawn on the appropriate scanline without making too big of a surface.)

The big mistake is right in the Scale= part. With the way I've coded it, it makes a decent trapezoid, but I'm not going for a trapezoid when ScaleStart=1. It should be trapezoidal with ScaleStart=0, though. Here's what I do know what I want: at the top of the screen, (if HLine=0) Scale should be ScaleStart. At the middle of the screen, (if HLine=view_hview/2) Scale should ALWAYS be 1. At the bottom of the screen, (if HLine=view_hview) Scale should be proportionately inverse.

This means, for what I want, if ScaleStart=1, Scale should be 1 ALL the way down. As such, ScaleStart should change the texture from a rectangular perspective (flat) to a trapezoidal perspective (3D-ish). ScaleStart could be a decimal.

I know it's possible, but I can't think of the right code. So, how do I do it? What is the proper formula for Scale?


over 3 years ago

I got an answer from someone else on the Game Maker Discord. Special thanks to Jüdacraz for giving me the answer.
First off, instead of thinking about (view_hview/2) for the point where Scale is 1, we've got a variable called Pivot, because that's the HLine where it pivots. Secondly, Jüdacraz gave me a cute little variable called Slope, determined by what I've already established.

		
			Slope=(1-ScaleStart)/Pivot
Scale= Zoom*( Slope*(HLine-Pivot)+1)
		
	

Again, thanks to Jüdacraz for your help!