Touch Controls

In the player class, I have if statements to determine the time to travel based on the resulting distance of the formula. But since it is returning wacky results, the player doesn’t ever move correctly. Short distances take forever (still), and long distances will still go by really quick, and sometimes be really slow.

@WilkeNine_Co - the distance between two points is simple

p1 = vec2(200,300)
p2 = vec2(100,400)
print(p1:dist(p2)) --prints the distance between them

This requires storing your points in vectors (vec2) as above, but that’s usually a good idea, because then you can write

p3=p1+p2 --adds two vectors
p3=p1*5 --multiplies a number by a vector
p1:angleBetween(p2) --angle between vectors
sprite(pic,p1.x,p1.y)

Have a look at the Vector section of the Reference link at top, for more. If you want to do anything useful with Codea, you should definitely learn to use vectors for screen positions.

If you want to move from point A to point B at a given speed, a simple way to do it is like this

--the starting data
speed=2 --pixels to move per frame
p1=vec2(100,200) --start point
p2=vec2(200,500) --end point

--the calculations
d=p1:dist(p2) --distance between points
--now calculate how much to move each frame
--note that m is a vec2, with an x and y value
m=(p2-p1)/d * speed
moving=true --used below

--and every frame, you do this
--the math.min makes sure you don't go past p2
if moving then
    p1=p1+math.min(m,p1:dist(p2))
    if p1==p2 then moving=false end
end

Make sure you understand the code fully, this is very important to learn. It leads on to “steering behaviour” which you will use later for making objects chase (or run away from) each other. I suggest you try googling “steering behaviour tutorial” to look for a simple explanation and guide on how to do it, because it’s fun.

@Ignatz once again, you’ve managed to make me feel dumb. Which is a good thing. I was looking through Vectors, but I totally missed “:dist()”. I use vectors for positions most of the time, in fact that’s what I use for my randomly generated chests for their positions. But since I was using Tweens for movement, I had to use tables. Thanks so much!

I feel dumb every day, it’s part of being a programmer.

And at the risk of making you feel dumb one last time, a vec2 is a table, so it works with a tween.

@Ignatz however, I did copy your movement solution into my touched(touch) function, and replaced my Player class’ self.position variable to a vec2(). And the Sprite’s positions are using that variables x and y values, but now it says there is a problem because of comparing userdata and something else.

…you want code right? Here it is:

Player Class:

Player = class()

function Player:init()
    --self.position = {x = 384, y = 512}
    self.position = vec2(WIDTH/2,HEIGHT/2)
    self.health = 100
    self.damage = 5
    self.gold = 200
end

function Player:draw()
    sprite("Documents:Character",self.position.x,self.position.y,40,40)
end

function Player:touched(touch)
    -- This is where we will manage the movement of the character, and also how the player will attack (if tapped an enemy).
    
    s=2 --pixels to move per frame
    p1=self.position--start point
    p2=vec2(touch.x,touch.y) --end point

    --the calculations
    d=p1:dist(p2) --distance between points
    --now calculate how much to move each frame
    --note that m is a vec2, with an x and y value
    m=(p2-p1)/d * s
    moving=true --used below

    --and every frame, you do this
    --the math.min makes sure you don't go past p2
    if moving then
        p1=p1+math.min(m,p1:dist(p2))
        if p1==p2 then moving=false end
    end

    --tween(timing,self.position,{x = touch.x, y = touch.y})
end

function Player:overlaps()
    if self.left > Sentinel().right or self.right < Sentinel().left or
    self.bottom > Sentinel().top or self.top < Sentinel().bottom then
        print("Touched a chest.")
    else
        return true
    end
end

@WilkieNine_Co - See numbered comments in the code and explanation below. You needed to adapt what I wrote, also I improved it.

function Player:init()
    --self.position = {x = 384, y = 512}
    self.position = vec2(WIDTH/2,HEIGHT/2)
    self.health = 100
    self.damage = 5
    self.gold = 200
    self.speed=2      -- speed.  [1]
end

function Player:draw()
    if self.endPosition then. --[2]
        self.position=self.position+
           self.direction*math.min(self.speed,self.position:dist(self.endPosition)) --[3]
        if self.position==self.endPosition then self.endPosition=nil end --[4]
    end
    sprite("Documents:Character",self.position.x,self.position.y,40,40)
end

function Player:touched(touch)
    -- This is where we will manage the movement of the character, and also how the player will attack (if tapped an enemy).
    self.endPosition=vec2(touch.x,touch.y) --end point.  --[5]
    --now calculate how much to move each frame
    local d=self.position:dist(self.endPosition) --distance between points
    self.direction=(self.endPosition-self.position)/d   --[6]
end

[1] define player speed in init

[2] first see point [5] below, then - as long as self.endPosition exists, we know we are moving and need to adjust our position.

[3] read point [6] first, then - we move by the lesser of our speed and the remaining distance, multiplied by the direction we stored.

[4] delete the end position when we get there

[5] set the end position and store it as a class variable so it will be remembered

[6] calculate direction we need to move in. We get this by subtracting current position from touch position. But the answer has a length, too. We want to go at a constant speed, so what we’ll do is divide our answer by its length, giving it a length of 1. Then if we want to move S pixels, we just multiply it by S. If this isnt’ clear, see my post here.

@Ignatz I see where this is going, but I am looking at everything, and I can’t seem to understand why there is an error saying.

“attempt to index a nil value (Global ‘Player’)”

I don’t see any square brackets in the code suggesting indexing. And there isn’t anything that goes “Player().”

it runs fine for me, so have a look at the rest of your code. Which line is breaking?

btw, there was an even easier solution, to use the tween and set the time as (d / self.speed) / 60, but you wouldn’t have learned as much about vectors that way.

“attempt to index a nil value (Global ‘Player’)”

You are not using classes correctly. In setup, do not explicitly call Player:init(). Instead, create an instance of the Player class, and assign it to a variable, eg player1 = Player(). You do not need :init(), init is run automatically when you create a new class instance. Then, in draw and in touched, you call the class’s methods by referring to the instance of the class you have created, so player1:draw(), player1:touched(touch) etc

@yojimbo2000
@Ignatz

Thanks for all your help, guys! I have figured out my own way of doing this. I got it all working now, and nothing is wrong with my code anymore. I made my own fix. I just used Ignatz’s mentioning of the vec2():dist() function, and combined that with my if statement approach.