Author Topic: Need Help With Enemy Scripting  (Read 217 times)

eliwolfe92

  • Newbie
  • *
  • Posts: 6
    • View Profile
Need Help With Enemy Scripting
« on: October 28, 2018, 07:08:22 pm »
I'm trying to create a hallway, not unlike like this one from ALttP...


Here's mine...


I unfortunately can't get my spike traps to act accordingly. Here's the script I made below...
Code: [Select]
-- 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.

Diarandor

  • Hero Member
  • *****
  • Posts: 1024
  • Cats are cool! (ΦωΦ)
    • View Profile
Re: Need Help With Enemy Scripting
« Reply #1 on: October 28, 2018, 09:03:38 pm »
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.
“If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you.”

eliwolfe92

  • Newbie
  • *
  • Posts: 6
    • View Profile
Re: Need Help With Enemy Scripting
« Reply #2 on: October 29, 2018, 12:16:38 am »
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).
Code: [Select]
-- 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

alexgleason

  • Jr. Member
  • **
  • Posts: 72
  • Vegan on a Desert Island
    • View Profile
    • Vegan on a Desert Island
Re: Need Help With Enemy Scripting
« Reply #3 on: October 29, 2018, 01:38:29 am »
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:

Code: Lua
  1. function enemy:on_created()
  2.   local x, y = self:get_position()
  3.   self._initial_x = x
  4.   self._initial_y = y
  5. end

Then I'd probably use 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() event (walls are obstacles). Also, it's possible that 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() 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 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.
« Last Edit: October 29, 2018, 01:46:48 am by alexgleason »
RIP Aaron Swartz

Max

  • Full Member
  • ***
  • Posts: 173
    • View Profile
Re: Need Help With Enemy Scripting
« Reply #4 on: October 30, 2018, 01:10:46 am »
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.

llamazing

  • Full Member
  • ***
  • Posts: 139
    • View Profile
Re: Need Help With Enemy Scripting
« Reply #5 on: October 30, 2018, 02:13:25 am »
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

wrightmat

  • Full Member
  • ***
  • Posts: 248
    • View Profile
Re: Need Help With Enemy Scripting
« Reply #6 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

alexgleason

  • Jr. Member
  • **
  • Posts: 72
  • Vegan on a Desert Island
    • View Profile
    • Vegan on a Desert Island
Re: Need Help With Enemy Scripting
« Reply #7 on: November 01, 2018, 03:26:44 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.
RIP Aaron Swartz

eliwolfe92

  • Newbie
  • *
  • Posts: 6
    • View Profile
Re: Need Help With Enemy Scripting
« Reply #8 on: November 03, 2018, 08:22:54 pm »
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
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...

I'm trying to use this:
Code: [Select]
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?

alexgleason

  • Jr. Member
  • **
  • Posts: 72
  • Vegan on a Desert Island
    • View Profile
    • Vegan on a Desert Island
Re: Need Help With Enemy Scripting
« Reply #9 on: November 03, 2018, 09:46:33 pm »
A really simple hack to make it go only left/right might be like this:

Code: Lua
  1. function enemy:go(direction4)
  2.   -- prevent this function from working for north/south movement
  3.   if direction4 == 1 or direction4 == 3 then
  4.     return
  5.   end
  6.  
  7.   -- 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
RIP Aaron Swartz

eliwolfe92

  • Newbie
  • *
  • Posts: 6
    • View Profile
Re: Need Help With Enemy Scripting
« Reply #10 on: November 03, 2018, 11:40:32 pm »
Thanks alex! testing this out now.