Skip to content

251. Fruit Ninja

December 9, 2015

I was looking for a new project, and then I wondered – “how hard is it to program something being cut in half, like in Fruit Ninja?”. Something like this

It turned out harder than I expected, but that’s good. I learned something from doing it, and I hope you will find it interesting, especially if you haven’t used Codea for very long, because there are some useful techniques.

This is quite a long post, but that’s because I am explaining everything. If you don’t have time to read it all, or it’s too basic for you, skip to the headings marked with an asterisk *, which have the most interesting stuff.

Swiping

The first problem is swiping – well, not the actual swiping, but figuring out whether our swipe hit an object, and where it hit it, so we know how to slice the object. Fruit Ninja just seems to break all the fruit exactly in half every time, but we’re going to slice more accurately, so if you just want to break a corner off, you can.

Drawing the actual swipe

Let’s start with the simplest problem – drawing the swipe on the screen as we are doing it. I’m going to draw a yellow line that follows my finger as it swipes.

Obviously, we’re going to use the touched function for this, and I’ll keep a little table that stores all the finger positions as I move my finger. Something like this.

  • when the touched state==BEGAN, put the current touch position in the table
  • when the touched state==MOVING, add the current touch position to the table
  • when the touched state==ENDED, clear all the items out of the table

And this is how the code looks

function touched(t)
    if t.state==BEGAN then 
        tp={vec2(t.x,t.y)} --tp is table of touch positions
    elseif t.state==MOVING then 
        local v=vec2(t.x,t.y)
        table.insert(tp,v)
    elseif t.state==ENDED then 
        tp={}
    end
end

Then, in my draw function, I’ll draw a line between each pair of points in this table, creating a line, when the finger is swiping. (If the finger is not swiping, then table tp is empty, so nothing is drawn).

--in the draw function
stroke(234, 229, 31) 
strokeWidth(5)
for j=1,#tp-1 do 
    line(tp[j].x,tp[j].y,tp[j+1].x,tp[j+1].y) 
end

Did we cut a fruit?

How do we know if we cut a fruit?

We could decide that if our finger just touches an object, then it breaks in half, but I want to be able to slice accurately, so I decided that I have to swipe right through an object before it splits.

swipe1This means I need to know the point where the swipe enters the object, and the point where it exits the object.

The line between them is where I’ll cut the object, as shown at left, where the green line is the swipe, and the red line is how I want to cut the object.

 

So I need to detect

  • when the swipe enters an object, and
  • when the swipe exits an object

What are the objects?

We can’t figure out if we are entering an object until we know what our object looks like. Well, they are just rectangular pictures, which are added using the function below.

First, I choose a random image for my object, from a little table I read in beforehand. These are just some pictures from the libraries provided with Codea. I store this image in img, and I store its width and height in w and h. Then I choose a random position above the top of the screen, and store that in pos.

The next 5 lines create a mesh, which in this case is just a set of six vertex positions that make two triangles in the shape of our rectangle. If you haven’t used meshes, don’t be put off, they are not difficult, and they are very important if you want to program any interesting graphics.

Finally, we add all this information to the fruit table, putting it in a little table of its own, with all the items named. Note that I have a speed item, which we will use to adjust the position, and a rot number which will rotate the object as it falls.

function AddFruit()
    local i=math.random(1,#images)
    local img=images[i] 
    local w,h=img.width,img.height
    local pos=vec2((0.2+0.6*math.random())*WIDTH,HEIGHT+100)
    local m=mesh() --create mesh
    m.vertices={vec2(-w/2,-h/2),vec2(w/2,-h/2),vec2(w/2,h/2),vec2(w/2,h/2),
        vec2(-w/2,h/2),vec2(-w/2,-h/2)}
    m.texCoords={vec2(0,0),vec2(1,0),vec2(1,1),vec2(1,1),vec2(0,1),vec2(0,0)}
    m.texture=img
    m:setColors(color(255))
    table.insert(fruit,{m=m,pos=pos,w=w,h=h,img=img,angle=0,rot=1,
         speed=vec2(0,-2),replace=true})
end

When I draw, I update the position and rotation, and draw each object. If it falls off the bottom, I delete it, and add a new item at the top again.

for i,f in pairs(fruit) do
    f.angle=f.angle+f.rot --rotate
    f.pos=f.pos+f.speed --move
    if f.pos.y<-50 then --off the screen, so..
        AddFruit()  --add a new item at the top
        table.remove(fruit,i) --delete this one
    else --draw it
        pushMatrix()
        translate(f.pos.x,f.pos.y)
        rotate(f.angle)
        f.m:draw()
        popMatrix()  
    end    
end  

Note that when you are drawing rotating objects, you first need to translate to the object position, then rotate it. This is because Codea always rotates around (0,0), and we want to rotate the object round its centre, so we have to make that centre (0,0), which we do by translating there. (The pushMatrix function stores the settings before I translate and rotate, and popMatrix puts them back the way they were afterwards).

Did our swipe enter or leave an object?

The function below tells me whether a touch point p is inside a rectangular object f (which is one of our falling objects, with a position pos, width w and height h). Nothing clever here.

function IsInsideFruit(f,p)
    if math.abs(p.x-f.pos.x)<f.w/2 and math.abs(p.y-f.pos.y)<f.h/2 then 
        return true 
    end
end

In my touched function, I loop through all the falling objects, checking whether the current touch position is inside them (using the function above). The code below sets an entry item to the current touch position, when the swipe first enters an object, and it sets an exit item when the swipe leaves the object again.

--f is the object
--tv is the touch point (x,y)
if IsInsideFruit(f,tv) then 
    f.enter = f.enter or tv
elseif f.enter and not f.exit then 
    f.exit=tv
    BreakFruit(i) --split the object
end

But my object is rotating!*

The IsInsideFruit function assumes our object is the right way up. But it isn’t. We’re rotating it. So how can we tell if our touch point is inside a rectangle which is at an angle?

We could figure out where the four corners of the rectangle are, and then use some fancy math to decide if our touch is inside it. But there is a much simpler way, if you can get your head around it.

rotateThe image on the left shows a rotated rectangle, plus a blue point that is outside and a red point that is inside it. If we rotate the rectangle and the two points back to an upright position, then we can use our IsInsideFruit function to tell us if the two points are inside the rectangle or not. This is easy, because we know the angle of the rectangle, so we need to rotate by the negative of that angle.

We don’t need to rotate the rectangle, because we already know we’re going to have an upright rectangle. All we need to do is rotate the touch point, by the negative of the rectangle angle, using the code below. To rotate a point needs the sin and cos of the angle, and because we are rotating around the centre of the rectangle, we need to

  1. subtract that centre position from the point we are rotating (so that (0,0) is now at the centre of the rectangle),
  2. rotate the point, and then
  3. add back the rectangle centre again
function Transform(f,p)
    local r=math.rad(-f.angle) 
    local s,c=math.sin(r),math.cos(r)
    local pp=p-f.pos --translate to centre of rectangle
    return vec2(pp.x*c-pp.y*s,pp.x*s+pp.y*c)+f.pos
end

So before I test if my touch point is inside an object, I first have to rotate it by the negative of the angle of that object.

Splitting an object*

This is the most interesting part of the project.

It is quite tricky, because the cut can be made in various ways, leaving us with two odd shaped objects. After thinking of different methods, I eventually decided this was the best.

I calculate the average of the entry and exit points, simply because I know this is inside the rectangle, and it is on the line joining the two parts I want to split, so I can use it as a central point for both parts of the split object.

splitIn this diagram, we have the rectangle, and a red line showing the split. I calculate the centre of the red line, shown by the red dot, and connect it to all the corners to make triangles. The corners which are above the red line go into one part, and those below go into the other, making two separate objects, made up of triangles in a mesh.

When I create the triangles, I need to ideally go anti-clockwise around the points in each of the two meshes, which means I need to sort them somehow. I decided to do this by calculating the rotation angle of the line from the red dot (alongside) to all the points, which I can do with the atan function.

So if my red dot is at (-5,25), and I want the angle to a corner at (-100,100), I subtract the red dot position to get (-105,75), and calculate the angle as atan2(75,-105). If the angle is between the angles for the two cut points (the left and right of the red line), that point goes into the first object, otherwise it goes into the second object.

So this is my process

  1. create two lists, one for each of the two parts of the object
  2. calculate the average of the entry and exit points
  3. calculate the angle of the entry and exit points, measured from the point in 2
  4. add the entry and exit points (and their angles) to both lists
  5. loop through the 4 corners, and calculate their angle. If it falls between the entry and exit points, add the point to the first list, otherwise add it to the second
  6. sort each list by angle, from smallest to largest
  7. now loop through all the points in each list, creating a series of triangles
  8. create two meshes from these triangles
  9. add them as falling objects, giving them opposite rotation and direction, so they spin away from each other
  10. delete the object that was split

 

And all of that gives you the video at the top.

The full code is here.

 

 

Leave a Comment

Leave a comment