I'm trying to create a hallway, not unlike like this one from ALttP...
(https://i.imgur.com/6g33aRM.png)
Here's mine...
(https://i.imgur.com/fd6KPZS.png)
I unfortunately can't get my spike traps to act accordingly. Here's the script I made below...
-- Lua script of enemy trap.
-- This script is executed every time an enemy with this model is created.
-- Feel free to modify the code below.
-- You can add more events and remove the ones you don't need.
-- See the Solarus Lua API documentation for the full specification
-- of types, events and methods:
-- http://www.solarus-games.org/doc/latest
local enemy = ...
local game = enemy:get_game()
local map = enemy:get_map()
local hero = map:get_hero()
local sprite
local movement
-- Event called when the enemy is initialized.
function enemy:on_created()
-- Initialize the properties of your enemy here,
-- like the sprite, the life and the damage.
sprite = enemy:create_sprite("enemies/" .. enemy:get_breed())
enemy:set_invincible(1)
enemy:set_damage(1)
end
-- Event called when the enemy should start or restart its movements.
-- This is called for example after the enemy is created or after
-- it was hurt or immobilized.
function enemy:on_restarted()
movement = sol.movement.create("path")
movement:set_path{4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
movement:set_speed(96)
movement:start(enemy)
path_movement:set_loop(true)
end
Two things I'm trying to get the code to do (unsuccessfully) are...
- Travel back and forth at a set speed on the same left/right path over and over
- Not be stopped by the hero, enemies, attacks, anything really. (to add to this I would like it to make that 'tink tink' noise when you slash it, shoot it, try to blow it up whatever)
I'm REALLY new to coding in general so I'd appreciate the input.
Some ideas:
1. I would use a straight movement from left to right and viceversa, instead. You can use some event to restart the movement when an obstacle is reached.
2. I think that setting the enemy traversable could fix the problem.
I went ahead and added traversable but it didn't seem to change anything as far as I can tell. I also changed the movement type to straight. Right now it moves to the left but I'm trying to figure out how to have it change direction when it reaches the wall (in this case).
-- Lua script of enemy trap.
-- This script is executed every time an enemy with this model is created.
-- Feel free to modify the code below.
-- You can add more events and remove the ones you don't need.
-- See the Solarus Lua API documentation for the full specification
-- of types, events and methods:
-- http://www.solarus-games.org/doc/latest
local enemy = ...
local game = enemy:get_game()
local map = enemy:get_map()
local hero = map:get_hero()
local sprite
local movement
-- Event called when the enemy is initialized.
function enemy:on_created()
-- Initialize the properties of your enemy here,
-- like the sprite, the life and the damage.
sprite = enemy:create_sprite("enemies/" .. enemy:get_breed())
enemy:set_invincible(1)
enemy:set_damage(1)
enemy:set_traversable(true)
end
-- Event called when the enemy should start or restart its movements.
-- This is called for example after the enemy is created or after
-- it was hurt or immobilized.
function enemy:on_restarted()
movement = sol.movement.create("straight")
movement:set_smooth(true)
movement:set_angle(3.14)
movement:set_speed(192)
movement:start(enemy)
end
This is an interesting challenge because the spikes could move either left or right from their initial position. I can think of many ways to go about it. A primitive solution would be to have 2 scripts: one for a left-aiming spike, one for a right-aiming one. Solarus 1.6 introduces custom entity properties, letting you configure whether a spike aims left or right by double clicking it and adding custom fields to each one.
But I think it makes more sense to assume that all spikes have a starting position, and they can move left *and* right depending on whether the hero triggers them in that direction. I don't think a Zelda game ever has a spike that can be triggered from both directions since they always start in a corner, but maybe this is how they actually are.
I'd consider storing the initial position of the enemy in the enemy itself, so you can come back to it. Something like this:
function enemy:on_created()
local x, y = self:get_position()
self._initial_x = x
self._initial_y = y
end
Then I'd probably use enemy:on_update() (http://www.solarus-games.org/doc/latest/lua_api_enemy.html#lua_api_enemy_on_update) (this gets called every frame) to constantly check the current position of the hero against the current position of the enemy, and if their Y values are within a certain range, activate the movement. Set the movement's angle based on difference between the X positions of the hero and the enemy.
There are also a few ways to determine when the enemy has reached the end and must reverse. I'd consider trying the entity:on_obstacle_reached() (http://www.solarus-games.org/doc/latest/lua_api_entity.html#lua_api_entity_on_obstacle_reached) event (walls are obstacles). Also, it's possible that movement:on_finished() (http://www.solarus-games.org/doc/latest/lua_api_movement.html#lua_api_movement_on_finished) might be called for a straight movement if the entity hits a wall? I'm not sure, but if so that's easier.
Keep in mind that movement:start() (http://www.solarus-games.org/doc/latest/lua_api_movement.html#lua_api_movement_start) also has a second optional callback parameter, which would be functionally the same as movement:on_finished() in this case.
To reverse back home, simply use a target movement (http://www.solarus-games.org/doc/latest/lua_api_target_movement.html#lua_api_target_movement_set_target) to the enemy's initial x/y
It's a good idea to use print() a lot when writing a script like this so you can take things one step at a time. For example, make it print "hero crossed paths!" when the hero enters a zone that should trigger the enemy, before you even make the enemy actually move.
Since you said you were new to programming I tried to be thorough. Please feel free to write more questions, or say so if you can't figure it out and I can write out more parts of the script.
I've made a spike trap enemy, and just heads up, it's surprisingly difficult. My method was to make it so they would go in any of the 4 directions if the hero's x/y lined up with theirs +/- 16px.
I think having the enemy check on_update() might be overkill. I usually run check_hero methods every 100ms or more, which have never been a noticeable delay. Idk what Solarus' framerate is, but having a timer run a checking method every 100ms is at last half the processing, if not less.
What that might look like is this:
function Enemy:check_hero()
--some code that gets the hero's coordinates and maybe calls another function if the hero is close or lined up
so.timer.start(enemy, 100, enemy:check_hero())
end
Basically, after the timer, it calls the function, which after the timer calls the function, which after the timer calls the function, which after the timer........
Christopho or someone might know better than me, but I don't think movement:on_finished() is called for interrupted movements. I would, just in case something weird happens, set a call back function for when the movement completes, as well as defining an on_obstacle_reached event.
One thing that, until 1.6 comes out, you could do is use the enemy's initial facing direction as a property to set which direction it's capable of going, but if you're totally new to coding, I'd recommend working on some different enemies first.
on_update() is called every 10ms which is also the minimum delay that can be set for a timer.
http://forum.solarus-games.org/index.php/topic,805.msg4379.html#msg4379
I believe I borrowed my pike script from one of christopho's games, and I don't recall having any issues with it.
https://github.com/wrightmat/zbom/blob/master/data/enemies/pike_detect.lua
Quote from: wrightmat on November 01, 2018, 02:24:28 PM
I believe I borrowed my pike script from one of christopho's games, and I don't recall having any issues with it.
https://github.com/wrightmat/zbom/blob/master/data/enemies/pike_detect.lua
Nice! This one goes all 4 directions.
Ohh, it also travels only a max of 8 squares in any direction (and ensures the hero is within that range).
It's checking on_obstacle_reached as well as on_movement_finished to decide whether to send it back to its original point. Obviously "on_obstacle_reached" would be triggered when hitting the hero, hitting any other blocking object, or hitting the wall if the wall is less than 8 squares away.
I really appreciate all the help everyone, I'm trying my best to get lua, well programming in general and all of your help is motivating to say the least. I found this: https://github.com/wrightmat/zbom/blob/master/data/enemies/pike_auto.lua (https://github.com/wrightmat/zbom/blob/master/data/enemies/pike_auto.lua)
which works perfectly for what I wanted in that hallway. I'm trying to break the script down now and see what it's doing so I can modify it as necessary.
Another hallway in the same dungeon...
(https://i.imgur.com/FrRKGpY.png)
I'm trying to use this:
local enemy = ...
local state = "stopped" -- "stopped", "moving", "going_back" or "paused".
local initial_xy = {}
local activation_distance = 24
-- Pike that moves when the hero is close.
function enemy:on_created()
self:set_life(1); self:set_damage(4)
self:create_sprite("enemies/pike_detect")
self:set_size(16, 16); self:set_origin(8, 13)
self:set_can_hurt_hero_running(true)
self:set_invincible()
initial_xy.x, initial_xy.y = self:get_position()
end
function enemy:on_update()
local hero = self:get_map():get_entity("hero")
if state == "stopped" and self:get_distance(hero) <= 192 then
-- Check whether the hero is close.
local x, y = self:get_position()
local hero_x, hero_y = hero:get_position()
local dx, dy = hero_x - x, hero_y - y
if math.abs(dy) < activation_distance then
if dx > 0 then self:go(0)
else self:go(2) end
end
if state == "stopped" and math.abs(dx) < activation_distance then
if dy > 0 then self:go(3)
else self:go(1) end
end
end
end
function enemy:go(direction4)
local dxy = {
{ x = 8, y = 0},
{ x = 0, y = -8},
{ x = -8, y = 0},
{ x = 0, y = 8}
}
-- Check that we can make the move.
local index = direction4 + 1
if not self:test_obstacles(dxy[index].x * 2, dxy[index].y * 2) then
state = "moving"
local x, y = self:get_position()
local angle = direction4 * math.pi / 2
local m = sol.movement.create("straight")
m:set_speed(192)
m:set_angle(angle)
m:set_max_distance(104)
m:set_smooth(false)
m:start(self)
end
end
function enemy:on_obstacle_reached()
self:go_back()
end
function enemy:on_movement_finished()
self:go_back()
end
function enemy:on_collision_enemy(other_enemy, other_sprite, my_sprite)
if other_enemy:get_breed() == self:get_breed() and state == "moving" then
self:go_back()
end
end
function enemy:go_back()
if state == "moving" then
state = "going_back"
local m = sol.movement.create("target")
m:set_speed(64)
m:set_target(initial_xy.x, initial_xy.y)
m:set_smooth(false)
m:start(self)
sol.audio.play_sound("sword_tapping")
elseif state == "going_back" then
state = "paused"
sol.timer.start(self, 500, function() self:unpause() end)
end
end
function enemy:unpause()
state = "stopped"
end
but the script only seems to use distance in all directions. does anyone know how to go about modding the script so that the pikes only activate on a specific X/Y coordinate? For instance in the picture above I'm trying to make it so the pikes only activate when you're on the same Y coordinate as they are on the map, and nothing else is in the way (tiles, entities, ect.) That may be too much just to modify this script as far as I know. Any thoughts?
A really simple hack to make it go only left/right might be like this:
function enemy:go(direction4)
-- prevent this function from working for north/south movement
if direction4 == 1 or direction4 == 3 then
return
end
-- rest of function is below
Point is, make the "go" function not do anything if the direction it would move is up (1) or down (3).
For direction4:
East = 0
North = 1
West = 2
South = 3
Thanks alex! testing this out now.