A Fish for your games

This can be used as a toy or part of a game (like I’m doing).
The behaviour of the animal is to swim around the screen, it has some adaptative algorithms to shape the parts of the body with the swimming movement. You can touch the screen and the fish can follow your finger XD

It is ready to add the online multiplayer layer to the code so you can have an online fishbowl.

To use it just create a fish = Fish(location,maxSpeed,maxForce), and then use fish:draw()

Boid = class()

-- constructor used by fish
function Boid:init(  _location, _maxSpeed,  _maxForce)
    self.velocity = vec2( math.random( -maxSpeed, maxSpeed ), math.random( -maxSpeed, maxSpeed ) )
    self:createBoid(_location, _maxSpeed, _maxForce)
end

-- constructor used by bubbles
--[[
function Boid:init(  _location,  _maxSpeed,  _maxForce, _velocity)
    self.velocity = _velocity
    self:createBoid(_location, _maxSpeed, _maxForce)
end
]]--
function Boid:createBoid( _location, _maxSpeed, _maxForce) 
    self.location         = _location
    self.maxSpeed         = _maxSpeed
    self.maxForce         = _maxForce
    self.acceleration     = vec2( 0, 0 )
    self.wanderTheta       = 0
    self.hasArrive         = false
end

function Boid:update()
    if not self.velocity then return end
    self.velocity = self.velocity + self.acceleration
    self.velocity = vec2( 
        math.min( self.velocity.x, self.maxSpeed),
        math.min( self.velocity.y, self.maxSpeed)
    )
    self.location = self.location + self.velocity
    self.acceleration = self.acceleration * 0
end

function Boid:debugRender()
    noStroke()
    fill(255, 0, 0,255)
    ellipse(self.location.x, self.location.y, 10, 10);
end

function Boid:steer( _target, _slowdown )
    local steer
    local desired = _target - self.location
    
    local dist = desired:len()
    
    if ( dist > 0 ) then
        desired = desired:normalize()
        
        if ( _slowdown and dist < 60 ) then
            desired = desired * ( self.maxSpeed * (dist / 60) )
            if ( dist < 10 ) then
                self.hasArrive = true
            end
        else 
            desired = desired * self.maxSpeed
        end
        
        steer = desired - self.velocity
        steer = vec2( 
            math.min(steer.x, self.maxForce ), 
            math.min(steer.y, self.maxForce) 
        )
    else 
        steer = vec2( 0, 0 )
    end
    
    return steer
end

function Boid:seek( _target ) 
    self.acceleration = self.acceleration + self:steer( _target, false ) 
end

function Boid:arrive(  _target ) 
    self.acceleration = self.acceleration + self:steer( _target, true )
end

function Boid:flee( _target )
    acceleration = acceleration - self:steer( _target, false ) 
end

function Boid:wander()
    local wanderR     = 5
    local wanderD     = 100
    local change     = 0.05
    
    self.wanderTheta = self.wanderTheta +  self.wanderTheta math.random( -change, change )
    
    local circleLocation = self.velocity
    circleLocation = circleLocation:normalize()
    circleLocation = (circleLocation * wanderD) + self.location
    
    local circleOffset = vec2(
        wanderR * math.cos(self.wanderTheta), 
        wanderR * math.sin(self.wanderTheta ) 
    )
    local target = circleLocation + circleOffset
    
    self:seek( target )
end

function Boid:evade(  _target ) 
    local lookAhead = self.location:dist( _target ) / (self.maxSpeed * 2)
    local predictedTarget = vec2( _target.x - lookAhead, _target.y - lookAhead )
    self:flee( predictedTarget )
end




Flagellum = class()
--[[    
    int numNodes
    
    float[][] spine
    
    float MUSCLE_RANGE     = 0.15
    float muscleFreq    = 0.08
    
    float sizeW, sizeH
    float spaceX, spaceY
    float theta
    float count
]]--    
    
function Flagellum:init( _sizeW, _sizeH,  _numNodes ) 
    self.MUSCLE_RANGE    = 0.15
    self.muscleFreq      = 0.08
    self.sizeW           = _sizeW
    self.sizeH           = _sizeH
    self.numNodes        = _numNodes
    self.spine           = {} --new float[numNodes][2]
    self.spaceX          = _sizeW / (_numNodes + 1.0)
    self.spaceY          = _sizeH / 2.0
    self.count           = 0
    self.theta           = math.pi
    self.thetaVel        = 0
    
    -- Initialize spine positions
    for n = 0, _numNodes-1 do
        local x = self.spaceX * n
        local y = self.spaceY
        self.spine[n] = {}
        self.spine[n][0] = x
        self.spine[n][1] = y
    end
    -- create mesh
    self.m = mesh()
end


function Flagellum:swim() 
    self.spine[0][0] = math.cos( self.theta )
    self.spine[0][1] = math.sin( self.theta )
    
    self.count = self.count + self.muscleFreq:len()
    local thetaMuscle = self.MUSCLE_RANGE * math.sin( self.count )
    
    self.spine[1][0] = -self.spaceX * math.cos( self.theta + thetaMuscle ) + self.spine[0][0]
    self.spine[1][1] = -self.spaceX * math.sin( self.theta + thetaMuscle ) + self.spine[0][1]
    
    for  n = 2 , self.numNodes - 1 do
        local x    = self.spine[n][0] - self.spine[n - 2][0]
        local y = self.spine[n][1] - self.spine[n - 2][1]
        local l = math.sqrt( (x * x) + (y * y) )
        
        if ( l > 0 ) then
            self.spine[n][0] = self.spine[n - 1][0] + (x * self.spaceX) / l
            self.spine[n][1] = self.spine[n - 1][1] + (y * self.spaceX) / l
        end
    end
end

function Flagellum:draw()
    self.m:draw()
    --self:debugRender()
end

function Flagellum:debugRender()
    for n = 0 ,self.numNodes - 1  do
        stroke( 0 )
        if ( n < self.numNodes - 1 ) then
            line( self.spine[n][0], self.spine[n][1], self.spine[n + 1][0], self.spine[n + 1][1] )
        end
        fill( 90 )
        ellipse( self.spine[n][0], self.spine[n][1], 6, 6 )
    end
end

```


Video:
www.youtube.com/watch?v=PRcYsOdSO5k

Capture:

Full code in the gist:
https://gist.github.com/juaxix/5956606

Tried it out but have one question how did you make the fish in the video move randomly mine only goes in one direction.

Add

function touched(touch)
 fish.mousePosition = vec2(touch.x,touch.y)
end

And Just touch the screen

Cool thanks!!

OK @Mnjk78lg

Start a new project. Put this in the Main tab:

-- Fish example

function setup()
    fish = Fish(
        vec2(WIDTH/2,HEIGHT/2), 
        math.random(2.0, 2.5 ), 
        0.9
    )

end

function draw()
  fish:draw()
end

function touched(touch)
 fish.mousePosition = vec2(touch.x,touch.y)
end

create a tab named Fish and paste all the code from the gist in there:
https://gist.github.com/juaxix/5956606

the order, first the Fish tab, as it contains the definition of the fish class, :wink:

@juaxix - I love anything “procedural” to do with content generation, it really gives an extra edge (and put’s the power back in the programmers hands).

Thanks for sharing.

Maybe you’ll like this procedural world too, from the game I’m writing
www.youtube.com/watch?v=cKYPzIs1rFA

I love the graphical style, the art looks really good - the land (wave) scape looks like it could do with being a bit smoother - as it’s a fish why not have the fish swim just under the water and have some physics that you have to fight against to get the fish to swim down and then use the buoyancy as it rises to increase the speed (if that makes sense).

The smoothness of the waves can be set with a param, look at the code in this post:
http://twolivesleft.com/Codea/Talk/discussion/1267/little-jumping-thing

I thought the same with the swimming but this fish is a fly-fish, look at the story, so he can fly over the seas :wink: