Phealax, that's a great example of UV animation using "SetSpriteUV".
I've just finished studying your code snippet, and as a result made
a fully commented new version with some improvements.
Attached are the ready to compile project files (without the EXE).
A preview of the code changes, can be seen in this snippet:
//=============================================================================
//### FILEHEADER
//-----------------------------------------------------------------------------
//
// Filename: main.agc
// Title: UV Animated Grass
//
// Version: 1.00, Build 0004
// Created: 2013-06-23 by Phaelax
// Last modified: 2013-08-31 by AgentSam
//
// Project state: Published on the AGK forums
// Release type: Public Domain
//
// Compatibility: Tested with AGK 108.12
//
// Programmer's notes
// ==================
//
// o 2013-08-30, AgentSam
// - The existance of this version is a side effect of my desire
// to study and improve Phealax's code snippet.
// - This code is based on the short UV-animation example,
// published by Phealax at:
// http://forum.thegamecreators.com/?m=forum_view&t=206300&b=41
//
// o 2013-08-30, AgentSam
// - This updated version of the example was published by AgentSam
// on the AGK forums in the original thread, whose link is above.
//
// o 2013-08-30, AgentSam
// - The wave offset is the least significant of the animation
// parameters and it's effect is so subtle that it may be
// difficult to notice. Use the keyboard controls to set the
// wave offset to 0.0, which will make it linear. Then set it
// somewhere around 82.0 to make it non-linear and you should
// be able to notice the difference!
//
// Revision History
// ================
//
// o 2013-08-30, AgentSam
// - NEW BUILD: 1.00, Build 0002
// - NOTES: Project started.
// - CHANGES: Added file header.
// - CHANGES: Major code refactoring and cleanup.
// - CHANGES: Moved terrain editing, terrain drawing,
// and grass animation to their own functions.
// - CHANGES: Added function headers and lots of comments.
//
// o 2013-08-30, AgentSam
// - NEW BUILD: 1.00, Build 0003
// - ADDITIONS: Added keyboard controls for:
// - AnimAmplitude
// - AnimFrequency
// - AnimPhaseOffset
//
// o 2013-08-31, AgentSam
// - NEW BUILD: 1.00, Build 0004
// - CHANGES: Grass bitmap is now wider (256x64), allowing
// the use of larger wave amplitude values.
// - CHANGES: Added the ability to directly edit (draw)
// and move terrain sections using the left
// mouse-button. (The old editing method was
// slow, so editing is much nicer now.)
//
// To Do
// =====
//
// o 2013-08-30, AgentSam
// - Reimplement Phaelax's "FPS-independent timing" routines;
// they work, but they're a bit of a mess.
//
// Author contact information
// ==========================
//
// o Phealax
// - Email: ---
// - Notes: original author of the grass animation example.
//
// o AgentSam
// - Email: ---
// - Notes: Modifications and code restructuring.
//
//---------------------------------------------------------------------------
//=============================================================================
//### SECTION - DISPLAY SETUP (VIRTUAL RESOLUTION)
//-----------------------------------------------------------------------------
// Landscape orientation using pixel based coordinates @UNLIMITED FPS
SetVirtualResolution (1024, 768)
SetSyncRate (0, 0)
SetResolutionMode (1)
SetSortTextures (0)
SetViewZoomMode (1)
// Set the allowed orientations - landscape only
SetOrientationAllowed (0, 0, 1, 1)
// Set mipmaps and filters
SetGenerateMipmaps (0)
SetDefaultMinFilter (1)
SetDefaultMagFilter (1)
// Set text output properties
SetPrintSize (16)
SetPrintColor (255, 255, 255)
// Set backbuffer properties
SetClearColor (0, 0, 0)
SetBorderColor (32, 32, 255)
// Error reporting mode
SetErrorMode (2)
//=============================================================================
//### SECTION - CONSTANTS
//-----------------------------------------------------------------------------
// Array bounds
#constant C_TERRAINPOINTS_TOTAL 8 // <-- Change this for more terrain sections
#constant C_GRASS_TOTAL 16 // <-- Change this for more grass sprites
// Math constants
#constant C_PI 3.14159265358979
// Boolean truth values
#constant TRUE 1
#constant FALSE 0
// Timing
#constant C_FPS_MAX 60.0
// Terrain
#constant C_TERRAIN_YPOS 600
// Scancodes
#constant KEY_ESCAPE 0x1B
#constant KEY_A 0x41
#constant KEY_B 0x42
#constant KEY_C 0x43
#constant KEY_D 0x44
#constant KEY_E 0x45
#constant KEY_F 0x46
#constant KEY_G 0x47
#constant KEY_H 0x48
#constant KEY_I 0x49
#constant KEY_J 0x4A
#constant KEY_K 0x4B
#constant KEY_L 0x4C
#constant KEY_M 0x4D
#constant KEY_N 0x4E
#constant KEY_O 0x4F
#constant KEY_P 0x50
#constant KEY_Q 0x51
#constant KEY_R 0x52
#constant KEY_S 0x53
#constant KEY_T 0x54
#constant KEY_U 0x55
#constant KEY_V 0x56
#constant KEY_W 0x57
#constant KEY_X 0x58
#constant KEY_Y 0x59
#constant KEY_Z 0x5A
//=============================================================================
//### SECTION - DATA TYPES
//-----------------------------------------------------------------------------
// Point
type TPoint2D
x as float
y as float
endtype
// Grass
type TGrassObject
sprite as integer
endtype
// Timing related values
type TFrameTime
fFPS as float
fAverageFPS as float
fTotalFrames as float
fFPSRatio as float
iFPSCount as integer
fwaveTime as float
fWaveTimeInc as float
endtype
//=============================================================================
//### SECTION - DECLARE GLOBAL VARIABLES
//-----------------------------------------------------------------------------
// Terrain values
global dim g_aTerrainPoints[C_TERRAINPOINTS_TOTAL] as TPoint2D
global g_fTerrainSpacing as float
// Grass values
global g_sprBase as integer
global dim g_aGrass[C_GRASS_TOTAL] as TGrassObject
global g_fGrassSpacing as float
// Animation values
global g_fAnimAmplitude as float
global g_fAnimFrequency as float
global g_fWavePhaseOffset as float
// Other values
global g_pTemp as TPoint2D
global g_iTemp as integer
global g_FrameTime as TFrameTime
// State flags
global g_bTerminate as integer
//=============================================================================
//### SECTION - INITIALIZE GLOBAL VARIABLES
//-----------------------------------------------------------------------------
// Terrain values
g_fTerrainSpacing = GetVirtualWidth() / C_TERRAINPOINTS_TOTAL
// Grass values
g_fGrassSpacing = GetVirtualWidth() / (C_GRASS_TOTAL-1)
// State flags
g_bTerminate = FALSE
// Animation values
g_fTime = 0.0
g_fAnimAmplitude = 0.1
g_fAnimFrequency = 0.4
g_fWavePhaseOffset = 64.0
// Values for time calculations
g_FrameTime.iFPSCount = 0
g_FrameTime.fTotalFrames = 0.0
g_FrameTime.fWaveTime = 0.0
// Initialize terrain object positions
for v_iIndex = 0 to C_TERRAINPOINTS_TOTAL
g_aTerrainPoints[v_iIndex].x = v_iIndex * g_fTerrainSpacing
g_aTerrainPoints[v_iIndex].y = C_TERRAIN_YPOS
next
//=============================================================================
//### SECTION - CREATE GRASS SPRITES
//-----------------------------------------------------------------------------
// Create base sprite for grass objects
g_sprBase = CreateSprite(LoadImage("grass3.png"))
SetSpriteOffset(g_sprBase, GetSpriteWidth(g_sprBase)/2, GetSpriteHeight(g_sprBase))
// Create grass sprites (via cloning the base sprite)
g_pTemp.y = C_TERRAIN_YPOS
for v_iIndex = 0 to C_GRASS_TOTAL
g_aGrass[v_iIndex].sprite = CloneSprite(g_sprBase)
g_pTemp.x = v_iIndex * g_fGrassSpacing
SetSpritePositionByOffset(g_aGrass[v_iIndex].sprite, g_pTemp.x, g_pTemp.y)
next
// Get rid of the base sprite
DeleteSprite(g_sprBase)
//=============================================================================
//### SECTION - MAIN LOOP
//-----------------------------------------------------------------------------
// Our main loop
do
// Update time
UpdateTimeValues()
// Manipulate terrain (allow user to edit the terrain)
TerrainEdit()
// Draw terrain
TerrainDraw()
// Do wavy-grass animation
AnimGrass(g_fAnimAmplitude, g_fAnimFrequency, g_fWavePhaseOffset)
// Show some information
ShowParams()
ShowHelp()
// Update screen
Sync()
// Handle keyboard input
HandleKbdInput()
// If user wants to quit, exit the loop...
if (g_bTerminate = TRUE) then exit
loop
// Terminate application
End
//=============================================================================
//### SECTION - SUPPORT ROUTINES
//-----------------------------------------------------------------------------
//=============================================================================
//# FUNCTION - ShowParams
//-----------------------------------------------------------------------------
//
// Purpose
// =======
// Show animation related values
//
// To call
// =======
// Takes no parameters
//
// Returns
// =======
// Nothing
//
// Dependencies
// ============
// None.
//
//---------------------------------------------------------------------------
//
function ShowParams()
Print("PARAMETERS")
Print("")
Print(" Amplitude = " + Str(g_fAnimAmplitude))
Print(" Frequency = " + Str(g_fAnimFrequency))
Print(" Phase offset = " + Str(g_fWavePhaseOffset))
Print("")
endfunction
//=============================================================================
//# FUNCTION - ShowHelp
//-----------------------------------------------------------------------------
//
// Purpose
// =======
// Output some info about controls
//
// To call
// =======
// Takes no parameters
//
// Returns
// =======
// Nothing
//
// Dependencies
// ============
// None.
//
//---------------------------------------------------------------------------
//
function ShowHelp()
Print("CONTROLS")
Print("")
Print(" LMB ... Terrain Edit")
Print("")
Print(" Q ..... Increment wave amplitude")
Print(" A ..... Decrement wave amplitude")
Print("")
Print(" W ..... Increment wave frequency")
Print(" S ..... Decrement wave frequency")
Print("")
Print(" E ..... Increment wave phase offset")
Print(" D ..... Decrement wave phase offset")
Print("")
Print(" R ..... Reset to defaults!")
Print(" ESC ... QUIT!")
endfunction
//=============================================================================
//# FUNCTION - HandleKbdInput
//-----------------------------------------------------------------------------
//
// Purpose
// =======
// Handle keyboard input.
//
// To call
// =======
// Takes no parameters
//
// Returns
// =======
// Nothing
//
// Dependencies
// ============
// Accesses the following globals:
// - g_bTerminate
// - g_fAnimAmplitude
// - g_fAnimFrequency
// - g_fWavePhaseOffset
// - g_fTerrainSpacing
// - g_aTerrainPoints
//
//---------------------------------------------------------------------------
//
function HandleKbdInput()
// Declare local variables
v_fStepSize as float
v_iIndex as integer
// Initialize local variables
v_fStepSize = (1 / g_FrameTime.fFPSRatio)
if (KeyDown(KEY_ESCAPE))
// Reset to defaults
g_bTerminate = TRUE
endif
if (KeyDown(KEY_R))
// Reset to defaults
g_fAnimAmplitude = 0.1
g_fAnimFrequency = 0.4
g_fWavePhaseOffset = 64.0
// Initialize terrain object positions
for v_iIndex = 0 to C_TERRAINPOINTS_TOTAL
g_aTerrainPoints[v_iIndex].x = v_iIndex * g_fTerrainSpacing
g_aTerrainPoints[v_iIndex].y = C_TERRAIN_YPOS
next
// Reorient grass along the terrain
RefactorGrass()
endif
if (KeyDown(KEY_Q))
// Increment amplitude
g_fAnimAmplitude = g_fAnimAmplitude + v_fStepSize * 0.01
endif
if (KeyDown(KEY_A))
// Decrement amplitude
g_fAnimAmplitude = g_fAnimAmplitude - v_fStepSize * 0.01
endif
if (KeyDown(KEY_W))
// Increment frequency
g_fAnimFrequency = g_fAnimFrequency + v_fStepSize * 0.1
endif
if (KeyDown(KEY_S))
// Decrement frequency
g_fAnimFrequency = g_fAnimFrequency - v_fStepSize * 0.1
endif
if (KeyDown(KEY_E))
// Increment wave offset
g_fWavePhaseOffset = g_fWavePhaseOffset + v_fStepSize * 0.5
endif
if (KeyDown(KEY_D))
// Decrement wave offset
g_fWavePhaseOffset = g_fWavePhaseOffset - v_fStepSize * 0.5
endif
// Clamp values to specified limits
g_fAnimAmplitude = Clamp(g_fAnimAmplitude, 0, 5)
g_fAnimFrequency = Clamp(g_fAnimFrequency, 0, 128)
g_fWavePhaseOffset = Clamp(g_fWavePhaseOffset, 0, 360)
endfunction
//=============================================================================
//# FUNCTION - UpdateTimeValues
//-----------------------------------------------------------------------------
//
// Purpose
// =======
// Updates timing values used for FPS-independent updates
//
// To call
// =======
// Takes no parameters
//
// Returns
// =======
// Nothing
//
// Dependencies
// ============
// Accesses the following global variables:
// - g_FrameTime
//
// Uses the following constants:
// - C_FPS_MAX
//
// Programmer's notes
// ==================
//
// o ScreenFPS() already returns the average FPS, so why does
// Plealax compute another average based on it?
//
//---------------------------------------------------------------------------
//
function UpdateTimeValues()
// Get current FPS
g_FrameTime.fFPS = ScreenFPS()
// Compute refresh ratio (not rate)
g_FrameTime.fFPSRatio = g_FrameTime.fFPS / C_FPS_MAX
// Increment frame counter
g_FrameTime.iFPSCount = g_FrameTime.iFPSCount + 1
// Compute average FPS
g_FrameTime.fTotalFrames = g_FrameTime.fTotalFrames + g_FrameTime.fFPS
g_FrameTime.fAverageFPS = g_FrameTime.fTotalFrames / g_FrameTime.iFPSCount
// Update time for grass animation
g_FrameTime.fWaveTimeInc = 0.5 / g_FrameTime.fFPSRatio
g_FrameTime.fWaveTime = g_FrameTime.fWaveTime + g_FrameTime.fWaveTimeInc
endfunction
//=============================================================================
//# FUNCTION - TerrainEdit
//-----------------------------------------------------------------------------
//
// Purpose
// =======
// Allow user to edit terrain using left mouse button.
//
// To call
// =======
// Takes no parameters
//
// Returns
// =======
// Nothing
//
// Dependencies
// ============
// Accesses the following global variables:
// - g_fTerrainSpacing
// - g_aTerrainPoints
// - g_FrameTime.fFPSRatio
//
//---------------------------------------------------------------------------
//
function TerrainEdit()
// Declare local variables
v_iTerrainSection as integer
v_bGrassUpdate as integer
// Initialize local variables
v_bGrassUpdate = FALSE // By default, assume mouse button is not pressed
v_iTerrainSection = GetPointerX() / g_fTerrainSpacing // Find out the current terrain section
// Make sure array index doesn't go out of bounds
v_iTerrainSection = Clamp(v_iTerrainSection, 0, C_TERRAINPOINTS_TOTAL)
// when LEFT MOUSE-BUTTON is held down...
if (GetRawMouseLeftState() = 1)
// Decrease Y-coordinate of this section
g_aTerrainPoints[v_iTerrainSection].y = GetPointerY()
// Set update flag
v_bGrassUpdate = TRUE
endif
// Update grass orientation, if necessary...
if (v_bGrassUpdate = TRUE)
// Reorient grass along the terrain
RefactorGrass()
endif
endfunction
//=============================================================================
//# FUNCTION - TerrainDraw
//-----------------------------------------------------------------------------
//
// Purpose
// =======
// Draw terrain (lines)
//
// To call
// =======
// Takes no parameters
//
// Returns
// =======
// Nothing
//
// Dependencies
// ============
// Accesses the following global variables:
// - g_aTerrainPoints
//
// Uses the following constants:
// - C_TERRAINPOINTS_TOTAL
//
//---------------------------------------------------------------------------
//
function TerrainDraw()
// Declare local variables
v_iIndex as integer
v_pTemp1 as TPoint2D
v_pTemp2 as TPoint2D
// Draw terrain
for v_iIndex = 1 to C_TERRAINPOINTS_TOTAL
v_pTemp1.x = g_aTerrainPoints[v_iIndex-1].x
v_pTemp1.y = g_aTerrainPoints[v_iIndex-1].y
v_pTemp2.x = g_aTerrainPoints[v_iIndex].x
v_pTemp2.y = g_aTerrainPoints[v_iIndex].y
DrawLine(v_pTemp1.x, v_pTemp1.y, v_pTemp2.x, v_pTemp2.y, 255, 0, 0)
next
endfunction
//=============================================================================
//# FUNCTION - AnimGrass
//-----------------------------------------------------------------------------
//
// Purpose
// =======
// Do grass animation via UV-offset manipulation.
//
// To call
// =======
// a_fAnimAmplitude = Amplitude of the wave effect (strength)
// a_fAnimFrequency = Frequency of thw wave effect (speed)
// a_fWavePhaseOffset = Wave offset of the top-right UV coordinate
//
// Returns
// =======
// Nothing
//
// Dependencies
// ============
//
// Accesses the following global variables:
// - g_FrameTime.fWaveTime
// - g_aGrass
//
// Uses the following constants:
// - C_PI
//
// Programmer's notes
// ==================
//
// o The value of "g_fWavePhaseOffset" at the end of the second
// "sin" calculation is the amount of sine-wave offset to be
// applied.
//
// It offsets the wave phase of the second UV-coordinate, so
// that the grass texture is allowed to expand and condense
// slightly non-linearly at both top corners; which creates
// a more natural "wind" effect.
//
//---------------------------------------------------------------------------
//
function AnimGrass(a_fAnimAmplitude as float, a_fAnimFrequency as float, a_fWavePhaseOffset as float)
// Declare local variables
v_iGrassIndex as integer
v_fGrassX as float
v_fV1UOffset as float
v_fV3UOffset as float
// Do wavy-grass animation
for v_iGrassIndex = 0 to C_GRASS_TOTAL
// Get sprite X-position
v_fGrassX = GetSpriteX(g_aGrass[v_iGrassIndex].sprite)
// Calculate wave phase and UV-offsets
v_fV1UOffset = a_fAnimAmplitude * Sin(a_fAnimFrequency*2*C_PI*g_FrameTime.fWaveTime + v_fGrassX)
v_fV3UOffset = a_fAnimAmplitude * Sin(a_fAnimFrequency*2*C_PI*g_FrameTime.fWaveTime + v_fGrassX+a_fWavePhaseOffset)
// Apply UV offsets
SetGrassUV(v_iGrassIndex, v_fV1UOffset, v_fV3UOffset)
next
endfunction
//=============================================================================
//# FUNCTION - RefactorGrass
//-----------------------------------------------------------------------------
//
// Purpose
// =======
// Rotates and positions grass sprites when terrain lines change
//
// To call
// =======
// Takes no parameters
//
// Returns
// =======
// Nothing
//
// Dependencies
// ============
// Accesses the following global variables:
// - g_fGrassSpacing
// - g_fTerrainSpacing
// - g_aTerrainPoints
// - g_aGrass
//
// Uses the following constants:
// - C_TERRAINPOINTS_TOTAL
// - C_GRASS_TOTAL
//
//---------------------------------------------------------------------------
//
function RefactorGrass()
// Declare local variables
v_iGrassIndex as integer
v_iTerrainIndex1 as integer
v_iTerrainIndex2 as integer
v_fDistance as float
v_fTerrainAngle as float
v_fGrassHeight as float
v_pDifference as TPoint2D
v_pGrass as TPoint2D
v_pTerrain1 as TPoint2D
v_pTerrain2 as TPoint2D
// Align grass objects with terrain
for v_iGrassIndex = 0 to C_GRASS_TOTAL
// Grass X-position
v_pGrass.x = v_iGrassIndex * g_fGrassSpacing
// Terrain points on either side of this grass object
v_iTerrainIndex1 = Floor(v_pGrass.x / g_fTerrainSpacing)
v_iTerrainIndex2 = Ceil(v_pGrass.x / g_fTerrainSpacing)
if (v_iTerrainIndex2 = 0) then v_iTerrainIndex2 = 1
if (v_iTerrainIndex1 = C_TERRAINPOINTS_TOTAL) then v_iTerrainIndex1 = C_TERRAINPOINTS_TOTAL-1
// Terrain points this grass is contained between
v_pTerrain1.x = g_aTerrainPoints[v_iTerrainIndex1].x
v_pTerrain1.y = g_aTerrainPoints[v_iTerrainIndex1].y
v_pTerrain2.x = g_aTerrainPoints[v_iTerrainIndex2].x
v_pTerrain2.y = g_aTerrainPoints[v_iTerrainIndex2].y
// Find new grass height
v_fGrassHeight = (v_pGrass.x - v_pTerrain1.x) / g_fTerrainSpacing
v_pGrass.y = v_pTerrain1.y + (v_pTerrain2.y - v_pTerrain1.y) * v_fGrassHeight
// Find distance between the two terrain points
v_pDifference.x = v_pTerrain1.x-v_pTerrain2.x
v_pDifference.y = v_pTerrain1.y-v_pTerrain2.y
v_fDistance = Sqrt(v_pDifference.x^2 + v_pDifference.y^2)
// Find angle of terrain
v_fTerrainAngle = Atanfull(v_pDifference.x / v_fDistance, v_pDifference.y / v_fDistance) + 90
// Set sprite properties for this grass object
SetSpriteAngle(g_aGrass[v_iGrassIndex].sprite, v_fTerrainAngle)
SetSpritePositionByOffset(g_aGrass[v_iGrassIndex].sprite, v_pGrass.x, v_pGrass.y)
next
endfunction
//=============================================================================
//# FUNCTION - SetGrassUV
//-----------------------------------------------------------------------------
//
// Purpose
// =======
// Applies UV offset to grass sprites.
//
// To call
// =======
// a_iGrassIndex = index of a grass sprite
// a_fV1UOffset = X-offset of the top-left UV-coordinate
// a_fV3UOffset = X-offset of the top-right UV-coordinate
//
// Returns
// =======
// Nothing
//
// Dependencies
// ============
// Accesses the following global variables:
// - g_aGrass
//
// Programmer's notes
// ==================
//
// o For an article about AGK UV coordinates, refer to:
// http://www.zimnox.com/resources/articles/tutorials/?ar=t004
//
//---------------------------------------------------------------------------
//
function SetGrassUV(a_iGrassIndex as integer, a_fV1UOffset as float, a_fV3UOffset as float)
// Declare local variables
v_fVertex1U as float
v_fVertex3U as float
// Initialize UV data
v_fVertex1U = 0 - a_fV1UOffset
v_fVertex3U = 1 - a_fV3UOffset
// Set UV data
SetSpriteUV(g_aGrass[a_iGrassIndex].sprite, v_fVertex1U, 0, 0, 1, v_fVertex3U, 0, 1, 1)
endfunction
//=============================================================================
//# FUNCTION - Keydown
//-----------------------------------------------------------------------------
//
// Purpose
// =======
// Check if the specified key is currently being held down.
//
// To call
// =======
// a_iScancode = scancode of key to check
//
// Returns
// =======
// TRUE = If key is down
// FALSE = If key is not down
//
// Dependencies
// ============
// None.
//
//---------------------------------------------------------------------------
//
function Keydown(a_iScancode as integer)
// Declare local variables
v_bKeydown as integer
// Initialize local variables
v_bKeyDown = FALSE
// Check current keystate
if (GetRawKeyState(a_iScancode))
// Set return value
v_bKeyDown = TRUE
endif
endfunction v_bKeyDown
//=============================================================================
//# FUNCTION - Clamp
//-----------------------------------------------------------------------------
//
// Purpose
// =======
// Clamps value to specified limits (minimum and maximum)
//
// To call
// =======
// a_fValue = value to clamp
// a_fMin = Minimum value to clamp to
// a_fMax = Maximum value to clamp to
//
// Returns
// =======
// If value is within limits, returns it unmodified.
// If value is outside bounds, clamps to min or max as appropriate.
//
// Dependencies
// ============
// None.
//
//---------------------------------------------------------------------------
//
function Clamp(a_fValue as float, a_fMin as float, a_fMax as float)
if a_fValue < a_fMin then exitfunction a_fMin
if a_fValue > a_fMax then exitfunction a_fMax
endfunction a_fValue
Cheers,
AgentSam