Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Topics - garsim

#1
Development / Some enemies
September 11, 2013, 11:41:55 PM
Hello, it's me again. I said in another topic that since I don't have enough stuff to present the project I'm working on, I'd share some scripts, so here I am.  :)
From now on, maybe it's not very spectacular, but maybe it can help beginners with some skills. ^^

I was working on some enemies from a game named Chip's Challenge (a really good retro game by the way) and tried to implement them as enemies for the Solarus quest editor.

Fireballs and gliders

First, I did the fireball and the glider, two enemies which go straight until they hurt an obstacle. In that case, they go to another direction :
- the fireball goes to its right by default ; and if it's not possible, it goes to his left ; and if it's not possible (if there are walls on its left and on its right) it comes back.
- the glider goes to its left by default ; and if it's not possible, it goes to his right ; and if it's not possible (if there are walls on its left and on its right) it comes back.

If you don't see what I mean, I suggest you watch this video of a Chip's Challenge level (the 100th one), where there are only fireballs (the orange monsters) and gliders (the other ones) : http://www.youtube.com/watch?v=12E6DDiHpVE&hd=1 . You should see their movements.


I'll begin with the fireball (the glider is not very different).
First, create a sprite with 4 directions. For the example, I took the pike one, in order to have a more "Zeldastic" sprite instead of the Chip's Challenge one.

walking enemies/pike.png 4 0 -1
0 0 16 16 8 13 1 1
0 0 16 16 8 13 1 1
0 0 16 16 8 13 1 1
0 0 16 16 8 13 1 1


Put the sprite in the appropriate place, then create the .lua script associated.

First, we declare some local variables :

local enemy = ...
local sprite
local next_direction


Then we do the enemy:on_created event. Do whatever you want, the only important thing is the line I put in bold : indeed, it will set a value for the sprite variable.

function enemy:on_created()

  self:set_life(1)
  self:set_damage(2)
  [b]sprite = self:create_sprite("enemies/pike_clockwise")[/b]
  self:set_size(16, 16)
  self:set_origin(8, 13)
  self:set_can_hurt_hero_running(true)
  self:set_invincible()
  self:set_attack_consequence("sword", "protected")
  self:set_attack_consequence("thrown_item", "protected")
  self:set_attack_consequence("arrow", "protected")
  self:set_attack_consequence("hookshot", "protected")
  self:set_attack_consequence("boomerang", "protected")
end



Let's do the enemy:on_restarted() event now. In this one, we'll tell our "pikeball" to go straight in its direction.
For this, we'll take the sprite direction and create a movement based on it :

function enemy:on_restarted()
  [b]next_direction = sprite:get_direction() * 2[/b]
  local m = sol.movement.create("path")
  m:set_path{next_direction}
  m:set_speed(128)
  m:set_loop(true)
  m:start(self)
end


Be careful with the line I put in bold, because it's a trap. You probably wonder why there is a "* 2". In fact, it's because of the numbering used in the Solarus engine.
You probably know there are eight possible directions, each one corresponding to a number :
  • 0 : right
  • 1 : top-right
  • 2 : top
  • 3 : top-left
  • 4 : left
  • 5 : bottom-left
  • 6 : bottom
  • 7 : bottom-right

In our case, the interesting directions/numbers are only horizontal and vertical ones (0, 2, 4 and 6).
The sprite we defined has four directions : nevertheless, they don't match with the ones we want. Indeed, the directions of the sprite are 0, 1, 2 and 3. So we have to multiply these directions by 2 to have the correct ones.


Then we can define the enemy:on_obstacle_reached() event, the most interesting one.
We can divide it into three parts :
  • Testing if the monster can go where it should (i.e. no obstacle on its right)
  • Define the monster's direction
  • Restart its movement

For the first part, we'll need a complex condition such like this :

if (next_direction == 2 and self:test_obstacles(16, 0)) -- If the monster was going up
    or (next_direction == 0 and self:test_obstacles(0, 16)) -- Was going right
    or (next_direction == 6 and self:test_obstacles(-16, 0)) -- Was going down
    or (next_direction == 4 and self:test_obstacles(0, -16)) -- Was going left
then 


If true, it means that there is an obstacle on the monster's right ; so it will have to go left instead.
Let's define the direction for the monster's left :

-- Then go to the monster's left
    next_direction = next_direction + 2
    if next_direction > 7 then
      next_direction = 0
    end


As we don't want the monster to have a diagonal movement, we add 2 to the next_direction variable. We also have to "reset" it if it was equal to 6 : indeed, 6 + 2 = 8, but "8" isn't a good direction. So we restart it.
Now, let's define the "normal" monster's behavior :

else -- The monster can go on its right
    next_direction = next_direction - 2
    if next_direction < 0 then
      next_direction = 6
    end
  end


It's like the previous piece of code, but in the other way.
Finally, let's make the monster restart in the new next_direction :

-- Set the direction and restart the movement
  sprite:set_direction(next_direction / 2)
  self:restart()
end


Note that you have to divide next_direction by 2 this time, for the same reason I was talking about some lines earlier.

Here's a tip. I previously said that the "pikeball" shall go back if it cannot go on this left nor on its right. But I didn't write anything about that case.
In fact, it's not necessary to write someting for it. Indeed, the first condition is quite efficient for that : it checks whether you can go on your right or not, and if not, the "pikeball" goes on its left. But if there's also something on the left, the enemy:on_obstacle_reached() event is activated again, and will test again if there is an obstacle on the right. And it will be the case since that obstacle is the one the "pikeball" hurt at first ; so its only choice will be left. And left + left = go back.
If you didn't understand what I said, don't worry, the most important thing to remember is that you don't have to bother with the "come back" case.  ;)


You can now insert your enemy in the quest editor and... enjoy !

Here is the complete .lua script for the fireball type enemy :

local enemy = ...

-- Enemy that always moves, horizontally or vertically depending on its direction
-- When it hurts an obstacle, it goes on its right ; if impossible, goes to the left or come back
-- If you know Chip's Challenge, it's like the fireball's movement ^^

local sprite
local next_direction

-- Do whatever you want for this part
function enemy:on_created()
 
  self:set_life(1)
  self:set_damage(2)
  sprite = self:create_sprite("enemies/pike_clockwise") -- But don't forget to define "sprite" and set a value
  self:set_size(16, 16)
  self:set_origin(8, 13)
  self:set_can_hurt_hero_running(true)
  self:set_invincible()
  self:set_attack_consequence("sword", "protected")
  self:set_attack_consequence("thrown_item", "protected")
  self:set_attack_consequence("arrow", "protected")
  self:set_attack_consequence("hookshot", "protected")
  self:set_attack_consequence("boomerang", "protected")
end

-- In this part, we define the straight movement
function enemy:on_restarted()

  next_direction = sprite:get_direction() * 2 -- Don't forget the "* 2" factor
  local m = sol.movement.create("path")
  m:set_path{next_direction}
  m:set_speed(128)
  m:set_loop(true)
  m:start(self)
end

-- In this part, we define the monster behavior when it hurts an obstacle
function enemy:on_obstacle_reached()

  -- Test : is there an obstacle on the monster's right ?
  if (next_direction == 2 and self:test_obstacles(16, 0)) -- If the monster was going up
    or (next_direction == 0 and self:test_obstacles(0, 16)) -- Was going right
    or (next_direction == 6 and self:test_obstacles(-16, 0)) -- Was going down
    or (next_direction == 4 and self:test_obstacles(0, -16)) -- Was going left
  then 
    -- Then go to the monster's left
    next_direction = next_direction + 2
    if next_direction > 7 then
      next_direction = 0
    end

  else -- The monster can go on its right
    next_direction = next_direction - 2
    if next_direction < 0 then
      next_direction = 6
    end
  end

  -- Set the direction and restart the movement
  sprite:set_direction(next_direction / 2)
  self:restart()
end



So much for the fireball type.
You can do the glider in order to practice. You'll just have to switch the directions in the enemy:on_obstacle_reached() event, it's as easy as pie.  ;)
#2
Hello, it's me again for another problem.  :-[

I wanted to change some sprites in the game, like the crystal switches and the solid ones.
So I modified the "miscellaneous.png" file like this (don't pay attention to the light blue switch I added, it will be used for a special purpose but I'm not using it yet) :




I didn't modify the .dat files related to this sheet.
And I put them on a test map (don't pay attention to the mess, it's just a test map  ;) ) :




But when I play and I use the sword on them, it's acting like I was using my sword on a wall (there is the sound "tick-tick") and do not activate them.


I had another problem of the same kind several days ago.
Like what I did with the switches, I recolored the example tileset from the tutorial videos, and I modified the entities, replacing bushes by flowers :




And when I play, bushes/flowers don't act as they should do, such as switches. When I use the sword, I'm just hurting a wall :





To put it in a nutshell, I just modified the .png files. And I can't see how that could modify tile properties like this...
I put this issue in the bugs section because it doesn't seem normal for me, but maybe I'm just using the sprites/tilesets not as I should do (we never know), and in that case this topic may go to the development section ?
#3
Hello !

I found a little bug in the 1.6.2 version of Mystery of Solarus XD (the one I found on solarus-games this morning).
In the cave with the bomb bag, we can push and pull the statues to obtain it. But when I push a statue on the bomb bag, I obtain it... and the statue follows me until it's blocked by another obstacle. The game can be continued after this bug, so it's not a frustrating one.
I don't know if it's voluntary (in this game, we never know...  ;D) but it's a little strange.
#4
Hello,

I wanted to use the conveyors, but with a little difference : indeed, I want to make them change direction (east-north-west-south-east-etc.).

I have this map with twelve conveyors respectively called "conveyor_1", "conveyor_2", etc. and a sensor which activates the rotation when Link crosses it :



As the documentation doesn't seem to indicate a method set_direction for conveyors, I made a script in the map.lua which removes and create again the conveyors in a new direction :

local current_conveyor_direction = 0

-- Conveyors position
local random_conveyors = {
{ x = 376, y = 1261 },
{ x = 440, y = 1261 },
{ x = 504, y = 1261 },
{ x = 568, y = 1261 },
{ x = 376, y = 1325 },
{ x = 440, y = 1325 },
{ x = 504, y = 1325 },
{ x = 568, y = 1325 },
{ x = 376, y = 1389 },
{ x = 440, y = 1389 },
{ x = 504, y = 1389 },
{ x = 568, y = 1389 }
}

function change_conveyors_direction()
-- Destruct conveyors before creating them again in another direction
map:remove_entities("conveyor_")

-- Change the direction of conveyors (0 : east, 2 : north, 4 : west, 6 : south)
current_conveyor_direction = current_conveyor_direction + 2
if current_conveyor_direction > 7 then
current_conveyor_direction = 0
end

-- Create conveyors
    for i = 1, 12, 1 do
map:create_conveyor_belt{
name = "conveyor_" .. i,
layer = 1,
x = random_conveyors[i].x,
y = random_conveyors[i].y,
direction = current_conveyor_direction
}
    end

    sol.timer.start(2000, change_conveyors_direction)
end

-- Activate the change of direction when Link walks on the sensor
function active_random_conveyors:on_activated()
change_conveyors_direction()
active_random_conveyors:set_enabled(false)
end


But when the function change_conveyors_direction is called for the second time, the game crashes :
Error: an entity with name 'conveyor_1' already exists.

So is the function map:remove_entities("conveyor_") not working, or are the conveyors removed only at the end of the function ?