Hi everybody,
I want to share some enemies that I have created with you. Could be useful for your projects. All of theses enemies are compatibles with this script http://forum.solarus-games.org/index.php/topic,51.0.html made by Christopho to reset enemies positions when crossing a separator.
I will add enemies time to time as I go in learning lua.
Homing bat
Simply, it is a bat that stands and do nothing. If you come close enough, it will go at you in a straight line. I used the Ganon firebat graphics for this, I changed the graphic script though to make it more realistic.
fire_bat.dat modified script:
animation{
name = "stopped",
src_image = "enemies/fire_bat.png",
directions = {
{ x = 160, y = 0, frame_width = 32, frame_height = 32, origin_x = 16, origin_y = 16, num_frames = 1 },
{ x = 160, y = 0, frame_width = 32, frame_height = 32, origin_x = 16, origin_y = 16, num_frames = 1 },
{ x = 160, y = 0, frame_width = 32, frame_height = 32, origin_x = 16, origin_y = 16, num_frames = 1 },
{ x = 160, y = 0, frame_width = 32, frame_height = 32, origin_x = 16, origin_y = 16, num_frames = 1 },
},
}
animation{
name = "walking",
src_image = "enemies/fire_bat.png",
frame_delay = 50,
frame_to_loop_on = 2,
directions = {
{ x = 96, y = 0, frame_width = 32, frame_height = 32, origin_x = 16, origin_y = 16, num_frames = 4 },
{ x = 96, y = 0, frame_width = 32, frame_height = 32, origin_x = 16, origin_y = 16, num_frames = 4 },
{ x = 96, y = 0, frame_width = 32, frame_height = 32, origin_x = 16, origin_y = 16, num_frames = 4 },
{ x = 96, y = 0, frame_width = 32, frame_height = 32, origin_x = 16, origin_y = 16, num_frames = 4 },
},
}
animation{
name = "hurt",
src_image = "enemies/fire_bat.png",
frame_delay = 50,
frame_to_loop_on = 0,
directions = {
{ x = 192, y = 0, frame_width = 32, frame_height = 32, origin_x = 16, origin_y = 16, num_frames = 2 },
{ x = 192, y = 0, frame_width = 32, frame_height = 32, origin_x = 16, origin_y = 16, num_frames = 2 },
{ x = 192, y = 0, frame_width = 32, frame_height = 32, origin_x = 16, origin_y = 16, num_frames = 2 },
{ x = 192, y = 0, frame_width = 32, frame_height = 32, origin_x = 16, origin_y = 16, num_frames = 2 },
},
}
homing bat script
local enemy = ...
local going_hero = false
local m
function enemy:on_created()
self:set_life(1)
self:set_damage(2)
self:create_sprite("enemies/fire_bat")
self:set_size(16, 8)
self:set_origin(8, 0)
self:set_obstacle_behavior("flying")
m = sol.movement.create("target")
m:set_ignore_obstacles(false)
m:set_speed(0)
m:start(self)
end
function enemy:on_restarted()
local sprite = self:get_sprite()
sprite:set_animation("stopped")
m:set_speed(0)
going_hero = false
self:go_random()
self:check_hero()
end
function enemy:check_hero()
local hero = self:get_map():get_entity("hero")
local _, _, layer = self:get_position()
local _, _, hero_layer = hero:get_position()
local near_hero = layer == hero_layer
and self:get_distance(hero) < 150
if near_hero and not going_hero then
self:go_hero()
sol.audio.play_sound("bat")
elseif not near_hero and going_hero then
self:go_random()
end
sol.timer.stop_all(self)
sol.timer.start(self, 100, function() self:check_hero() end)
end
function enemy:go_random()
going_hero = false
local sprite = self:get_sprite()
sprite:set_animation("stopped")
m:set_speed(0)
end
function enemy:go_hero()
going_hero = true
local sprite = self:get_sprite()
sprite:set_animation("walking")
m:set_speed(88)
end
Guruguru bar
This is simply a reproduction of this ennemy: http://zeldawiki.org/Guruguru_Bar
I made two version, one clockwise and the other counter clockwise. Theses two enemies do not have graphics, they are simply the center of the firebar. The firebar itself is composed of another enemy.
I used the graphics of super mario world for the fireballs. See attached.
Here is the fireBall sprite script:
animation{
name = "walking",
src_image = "enemies/fireBall.png",
frame_delay = 50,
frame_to_loop_on = 0,
directions = {
{ x = 0, y = 0, frame_width = 16, frame_height = 16, origin_x = 0, origin_y = 0, num_frames = 2 },
{ x = 0, y = 0, frame_width = 16, frame_height = 16, origin_x = 0, origin_y = 0, num_frames = 2 },
{ x = 0, y = 0, frame_width = 16, frame_height = 16, origin_x = 0, origin_y = 0, num_frames = 2 },
{ x = 0, y = 0, frame_width = 16, frame_height = 16, origin_x = 0, origin_y = 0, num_frames = 2 },
},
}
The fireball ennemy:
local enemy = ...
function enemy:on_created()
self:set_life(1)
self:set_damage(2)
self:create_sprite("fireBall")
self:set_size(16, 16)
self:set_origin(8, 8)
self:set_invincible()
self:set_obstacle_behavior("flying")
end
-- Circles around the enemy that created it
function enemy:go_circle(center_entity, rayon, notRev)
local m = sol.movement.create("circle")
m:set_center(center_entity, -8, -29)
m:set_radius(rayon)
m:set_angle_speed(88)
m:set_ignore_obstacles(true)
m:set_clockwise(notRev)
m:start(self)
end
Clockwise firebar:
local enemy = ...
local son, son2, son3, son4
function enemy:on_created()
self:set_life(1)
self:set_damage(0)
self:set_size(16, 16)
self:set_origin(8, 8)
self:set_invincible()
self:set_obstacle_behavior("flying")
son = self:create_enemy{
name = son_name,
breed = "fireBall",
x = 0,
y = 0,
layer = 0,
}
son:go_circle(self, 16, true)
son:set_optimization_distance(0) -- We do not want the enemy to ever stop if player is too far,
-- take note this is consuming resourcces. Use disabled() function stop.
son2 = self:create_enemy{
name = son_name,
breed = "fireBall",
x = 0,
y = 0,
layer = 0,
}
son2:go_circle(self, 32, true)
son2:set_optimization_distance(0)
son3 = self:create_enemy{
name = son_name,
breed = "fireBall",
x = 0,
y = 0,
layer = 0,
}
son3:go_circle(self, 48, true)
son3:set_optimization_distance(0)
son4 = self:create_enemy{
name = son_name,
breed = "fireBall",
x = 0,
y = 0,
layer = 0,
}
son4:go_circle(self, 64, true)
son4:set_optimization_distance(0)
son:set_enabled(true)
son2:set_enabled(true)
son3:set_enabled(true)
son4:set_enabled(true)
end
function enemy:on_restarted()
son:set_enabled(true)
son2:set_enabled(true)
son3:set_enabled(true)
son4:set_enabled(true)
son:go_circle(self, 16, true)
son:set_optimization_distance(0)
son2:go_circle(self, 32, true)
son2:set_optimization_distance(0)
son3:go_circle(self, 48, true)
son3:set_optimization_distance(0)
son4:go_circle(self, 64, true)
son4:set_optimization_distance(0)
end
function enemy:on_disabled()
son:set_enabled(false)
son2:set_enabled(false)
son3:set_enabled(false)
son4:set_enabled(false)
end
Counter clockwise firebar:
local enemy = ...
local son, son2, son3, son4
function enemy:on_created()
self:set_life(1)
self:set_damage(0)
self:set_size(16, 16)
self:set_origin(8, 8)
self:set_invincible()
self:set_obstacle_behavior("flying")
son = self:create_enemy{
name = son_name,
breed = "fireBall",
x = 0,
y = 0,
layer = 0,
}
son:go_circle(self, 16, false)
son:set_optimization_distance(0)
son2 = self:create_enemy{
name = son_name,
breed = "fireBall",
x = 0,
y = 0,
layer = 0,
}
son2:go_circle(self, 32, false)
son2:set_optimization_distance(0)
son3 = self:create_enemy{
name = son_name,
breed = "fireBall",
x = 0,
y = 0,
layer = 0,
}
son3:go_circle(self, 48, false)
son3:set_optimization_distance(0)
son4 = self:create_enemy{
name = son_name,
breed = "fireBall",
x = 0,
y = 0,
layer = 0,
}
son4:go_circle(self, 64, false)
son4:set_optimization_distance(0)
son:set_enabled(true)
son2:set_enabled(true)
son3:set_enabled(true)
son4:set_enabled(true)
end
function enemy:on_restarted()
son:set_enabled(true)
son2:set_enabled(true)
son3:set_enabled(true)
son4:set_enabled(true)
son:go_circle(self, 16, false)
son:set_optimization_distance(0)
son2:go_circle(self, 32, false)
son2:set_optimization_distance(0)
son3:go_circle(self, 48, false)
son3:set_optimization_distance(0)
son4:go_circle(self, 64, false)
son4:set_optimization_distance(0)
end
function enemy:on_disabled()
son:set_enabled(false)
son2:set_enabled(false)
son3:set_enabled(false)
son4:set_enabled(false)
end
Nice scripts, I like them. I will share some of my enemies when they'll become enough debugged :p
Here is a new entity I have made.
This is simply a bumper. It has to be 16 x 16, I still have to work the code to make it flexible. It is pretty much the same as the bumpers in ALTTP except it is 16 x 16 instead of 32 x 32.
local entities = ...
local boingIsReady = true
--TODO: Make it compatible with separator manager script
function entities:on_created()
-- Call a function every 22 milliseconds.
sol.timer.start(22, function()
if(boingIsReady == true) then
-- Get hero position
local hero_x, hero_y, hero_layer = self:get_map():get_entity("hero"):get_position()
-- When changed in engine, get hero width & height here
local hero_width = 16
local hero_height = 16
-- Get entity position
local ent_x, ent_y, ent_layer = self:get_position()
local ent_width, ent_height = self:get_size()
--TODO: change this line, I hate it, not clean at all
if( ( (ent_x - 12) < hero_x) and ( (ent_x + ent_width - 3) > hero_x) and ( (ent_y - 12) < hero_y) and ( (ent_y - 11 + ent_height) > hero_y) ) then
--self:get_map():get_entity("hero"):set_position(hero_x, hero_y + 1, hero_layer)
self:get_map():get_entity("hero"):freeze()
-- Get center point of entity and hero
local ent_center_x = ent_x + (ent_width / 2)
local ent_center_y = ent_y + (ent_height / 2)
local hero_center_x = hero_x + (hero_width / 2)
local hero_center_y = hero_y + (hero_height / 2)
-- Get delta x and delta y
local delta_x = hero_center_x - ent_center_x
local delta_y = hero_center_y - ent_center_y
local angle = 0
--Check if value is 0 or else division by 0
if(delta_x == 0) then
if(delta_y <= 0) then
angle = math.pi / 2
else
angle = 3 * math.pi / 2
end
else
-- Get angle with arctan
angle = math.atan(delta_y / delta_x)
-- Check the side of the angle
if(ent_center_x < hero_center_x) then
-- reverse angle, for some weird reason, atan start it's angle at pi instead of 0... Correct me if I am wrong
angle = angle * -1
elseif(ent_center_x > hero_center_x) then
-- Substract from pi
angle = math.pi - angle
end
end
-- Set movement stuff
local m = sol.movement.create("straight")
m:set_speed(130)
m:set_max_distance(32)
m:get_ignore_obstacles(false)
-- Set movement angle
m:set_angle(angle)
-- Start movement
m:start(self:get_map():get_entity("hero"))
-- Boing!
sol.audio.play_sound("bounce")
-- Lock function
boingIsReady = false
sol.timer.start(300, function()
-- Check if hero is still freezed, if not, do nothing
if( self:get_map():get_entity("hero"):get_state() == "freezed") then
self:get_map():get_entity("hero"):unfreeze()
end
-- Unlock function
boingIsReady = true
end)
end --If position
end --if(boingIsReady == true)
return true
end) --main timer
end