RPG tile movement

Started by wombats, June 17, 2020, 22:53:39

Previous topic - Next topic

wombats

Hi,

Could someone help me fix this code for RPG tile-based movement, please? For some reason it works faster in some directions than others and the animation doesn't work as it should. Or if there's a better way of doing this, I'm all ears!

Spritesheet: https://opengameart.org/content/antifareas-rpg-sprite-set-1-enlarged-w-transparent-background-fixed

' Spritesheet from https://opengameart.org/content/antifareas-rpg-sprite-set-1-enlarged-w-transparent-background-fixed

SuperStrict

Graphics 640, 480

Const STATE_IDLE:Int = 0
Const STATE_WALKING:Int = 1

Const DIR_UP:Int = 0
Const DIR_DOWN:Int = 1
Const DIR_LEFT:Int = 2
Const DIR_RIGHT:Int = 3

Global tileSize:Int = 32

Type TPlayer
Field sprite:TImage
Field x:Int, y:Int, w:Int, h:Int
Field state:Int = STATE_IDLE
Field moveDir:Int = -1
Field direction:Int = DIR_DOWN
Field nextTile:Int
Field animSpeed:Int
Field lastChanged:Int
Field frame:Int
Field moveSpeed:Float

Function Create:TPlayer(file:String, x:Int, y:Int, w:Int, h:Int, animSpeed:Int, moveSpeed:Float)
Local player:TPlayer = New TPlayer
player.sprite = LoadImage(file)
player.state = STATE_IDLE
player.x = x * tileSize
player.y = y * tileSize
player.w = w
player.h = h
player.animSpeed = animSpeed
player.moveSpeed = moveSpeed / tileSize
Return player
EndFunction

Method Draw()
Local clipX:Int, clipY:Int
Select direction
Case DIR_UP
clipY = 0
Case DIR_DOWN
clipY = 2 * h
Case DIR_RIGHT
clipY = 1 * h
Case DIR_LEFT
clipY = 3 * h
EndSelect
If state = STATE_IDLE
Select direction
Case DIR_UP
clipX = 1 * w
Case DIR_DOWN
clipX = 1 * w
Case DIR_RIGHT
clipX = 1 * w
Case DIR_LEFT
clipX = 1 * w
EndSelect
ElseIf state = STATE_WALKING
If frameTime > lastChanged + animSpeed
If frame < 2
frame:+1
ElseIf frame = 2
frame = 1
EndIf
clipX = frame * w
lastChanged = frameTime
EndIf
EndIf
DrawSubImageRect(sprite, x, y, w, h, clipX, clipY, w, h)
EndMethod

Method HandleInput()
If state = STATE_IDLE
If KeyDown(KEY_UP)
nextTile = y - tileSize
moveDir = DIR_UP
state = STATE_WALKING
direction = DIR_UP
EndIf
If KeyDown(KEY_LEFT)
nextTile = x - tileSize
moveDir = DIR_LEFT
state = STATE_WALKING
direction = DIR_LEFT
EndIf
If KeyDown(KEY_DOWN)
nextTile = y + tileSize
moveDir = DIR_DOWN
state = STATE_WALKING
direction = DIR_DOWN
EndIf
If KeyDown(KEY_RIGHT)
nextTile = x + tileSize
moveDir = DIR_RIGHT
state = STATE_WALKING
direction = DIR_RIGHT
EndIf
EndIf
EndMethod

Method HandleMovement()
If state = STATE_WALKING
Select moveDir
Case DIR_UP
y:-moveSpeed
If y <= nextTile
y = nextTile
state = STATE_IDLE
moveDir = -1
direction = DIR_UP
EndIf
Case DIR_DOWN
y:+moveSpeed
If y => nextTile
y = nextTile
state = STATE_IDLE
moveDir = -1
direction = DIR_DOWN
EndIf
Case DIR_LEFT
x:-moveSpeed
If x <= nextTile
x = nextTile
state = STATE_IDLE
moveDir = -1
direction = DIR_LEFT
EndIf
Case DIR_RIGHT
x:+moveSpeed
If x => nextTile
x = nextTile
state = STATE_IDLE
moveDir = -1
direction = DIR_RIGHT
EndIf
EndSelect
EndIf
EndMethod

EndType

Global player:TPlayer, frameTime:Int

player = TPlayer.Create("healer_f.png", 5, 5, 32, 36, 200, 50.0)

While Not AppTerminate() And Not KeyHit(KEY_ESCAPE)

player.HandleInput()

Cls

player.HandleMovement()
player.Draw()

Flip

frameTime = MilliSecs()

Wend

_PJ_

Since each animation is comprised of 3 frames and that it is not possible to WALK in-place then I would simply set the current frame for STATE_WALKING state to be the coordinate position MOD 3
This would force animation to always be at the actual rate at which the sprite moves.

Using Modulo would really help with someof your nested IFs.

_

Then rather than DrawSubImageRect of the entire sprite sheet, why not just load in Sprites as animated image sequences, so instead of having to calculate the pixel dimensions of each frame, just load in the section of the spritesheet as an animimage with frames that correspond such as::

(I'm not referring to your actual case, so the numbers prtobably wont match the actual sprites)
ANIM_UP_STARTFRAME = 0
ANIM_RIGHT_STARTFRAME = 3
ANIM_DOWN_STARTFRAME=6
ANIM_DOWN_STARTFRAME=6

So then you only need to display that sprite as

DrawImage(Sprite, X, Y, ANIMSEQUENCESTARTFRAME + (CoordOffset Mod 3))

___

The FRAMETIME global nly changes at the END of every frame, so essentially, it captures the ntire time it took to complete the previous frame. Assuming a 60 Hz rate, this is 16.6667 or so millisecs

Your animation timer checks for this to be:

frameTime > lastChanged + animSpeed


Since the character is created with animspeed of 200, this will NEVER be true, so the anim frames are always resetting.

__

I think you should probably use either a dedicated timer, or use Deltatiming based on a zero point at the start of each frame if you insist on animating according to a set time interval.

Derron

Quote from: _PJ_ on July 17, 2020, 14:09:57
Then rather than DrawSubImageRect of the entire sprite sheet, why not just load in Sprites as animated image sequences, so instead of having to calculate the pixel dimensions of each frame, just load in the section of the spritesheet as an animimage with frames that correspond such as::

As loading an "animimage" leads to each animation sprite becoming an own texture. Texture switch minimization leads to a better performance (more FPS).


Create a sprite class which contains its position on the spritesheet and handle the drawing stuff in there. This hides the drawsubimagerect-or-whatever-long-and-complex-functions from normal game specific code.



bye
Ron