diff options
Diffstat (limited to 'samples/05_mouse/05_mouse_move/app/main.rb')
| -rw-r--r-- | samples/05_mouse/05_mouse_move/app/main.rb | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/samples/05_mouse/05_mouse_move/app/main.rb b/samples/05_mouse/05_mouse_move/app/main.rb new file mode 100644 index 0000000..97edbe7 --- /dev/null +++ b/samples/05_mouse/05_mouse_move/app/main.rb @@ -0,0 +1,296 @@ +=begin + + Reminders: + + - num1.greater(num2): Returns the greater value. + For example, if we have the command + puts 4.greater(3) + the number 4 would be printed to the console since it has a greater value than 3. + Similar to lesser, which returns the lesser value. + + - find_all: Finds all elements of a collection that meet certain requirements. + For example, in this sample app, we're using find_all to find all zombies that have intersected + or hit the player's sprite since these zombies have been killed. + + - args.inputs.keyboard.key_down.KEY: Determines if a key is being held or pressed. + Stores the frame the "down" event occurred. + For more information about the keyboard, go to mygame/documentation/06-keyboard.md. + + - args.outputs.sprites: An array. The values generate a sprite. + The parameters are [X, Y, WIDTH, HEIGHT, PATH, ANGLE, ALPHA, RED, GREEN, BLUE] + For more information about sprites, go to mygame/documentation/05-sprites.md. + + - args.state.new_entity: Used when we want to create a new object, like a sprite or button. + When we want to create a new object, we can declare it as a new entity and then define + its properties. (Remember, you can use state to define ANY property and it will + be retained across frames.) + + - String interpolation: Uses #{} syntax; everything between the #{ and the } is evaluated + as Ruby code, and the placeholder is replaced with its corresponding value or result. + + - map: Ruby method used to transform data; used in arrays, hashes, and collections. + Can be used to perform an action on every element of a collection, such as multiplying + each element by 2 or declaring every element as a new entity. + + - sample: Chooses a random element from the array. + + - reject: Removes elements that meet certain requirements. + In this sample app, we're removing/rejecting zombies that reach the center of the screen. We're also + rejecting zombies that were killed more than 30 frames ago. + +=end + +# This sample app allows users to move around the screen in order to kill zombies. Zombies appear from every direction so the goal +# is to kill the zombies as fast as possible! + +class ProtectThePuppiesFromTheZombies + attr_accessor :grid, :inputs, :state, :outputs + + # Calls the methods necessary for the game to run properly. + def tick + defaults + render + calc + input + end + + # Sets default values for the zombies and for the player. + # Initialization happens only in the first frame. + def defaults + state.flash_at ||= 0 + state.zombie_min_spawn_rate ||= 60 + state.zombie_spawn_countdown ||= random_spawn_countdown state.zombie_min_spawn_rate + state.zombies ||= [] + state.killed_zombies ||= [] + + # Declares player as a new entity and sets its properties. + # The player begins the game in the center of the screen, not moving in any direction. + state.player ||= state.new_entity(:player, { x: 640, + y: 360, + attack_angle: 0, + dx: 0, + dy: 0 }) + end + + # Outputs a gray background. + # Calls the methods needed to output the player, zombies, etc onto the screen. + def render + outputs.solids << [grid.rect, 100, 100, 100] + render_zombies + render_killed_zombies + render_player + render_flash + end + + # Outputs the zombies on the screen and sets values for the sprites, such as the position, width, height, and animation. + def render_zombies + outputs.sprites << state.zombies.map do |z| # performs action on all zombies in the collection + z.sprite = [z.x, z.y, 4 * 3, 8 * 3, animation_sprite(z)].sprite # sets definition for sprite, calls animation_sprite method + z.sprite + end + end + + # Outputs sprites of killed zombies, and displays a slash image to show that a zombie has been killed. + def render_killed_zombies + outputs.sprites << state.killed_zombies.map do |z| # performs action on all killed zombies in collection + z.sprite = [z.x, + z.y, + 4 * 3, + 8 * 3, + animation_sprite(z, z.death_at), # calls animation_sprite method + 0, # angle + 255 * z.death_at.ease(30, :flip)].sprite # transparency of a zombie changes when they die + # change the value of 30 and see what happens when a zombie is killed + + # Sets values to output the slash over the zombie's sprite when a zombie is killed. + # The slash is tilted 45 degrees from the angle of the player's attack. + # Change the 3 inside scale_rect to 30 and the slash will be HUGE! Scale_rect positions + # the slash over the killed zombie's sprite. + [z.sprite, [z.sprite.rect, 'sprites/slash.png', 45 + state.player.attack_angle_on_click, z.sprite.a].scale_rect(3, 0.5, 0.5)] + end + end + + # Outputs the player sprite using the images in the sprites folder. + def render_player + state.player_sprite = [state.player.x, + state.player.y, + 4 * 3, + 8 * 3, "sprites/player-#{animation_index(state.player.created_at_elapsed)}.png"] # string interpolation + outputs.sprites << state.player_sprite + + # Outputs a small red square that previews the angles that the player can attack in. + # It can be moved in a perfect circle around the player to show possible movements. + # Change the 60 in the parenthesis and see what happens to the movement of the red square. + outputs.solids << [state.player.x + state.player.attack_angle.vector_x(60), + state.player.y + state.player.attack_angle.vector_y(60), + 3, 3, 255, 0, 0] + end + + # Renders flash as a solid. The screen turns white for 10 frames when a zombie is killed. + def render_flash + return if state.flash_at.elapsed_time > 10 # return if more than 10 frames have passed since flash. + # Transparency gradually changes (or eases) during the 10 frames of flash. + outputs.primitives << [grid.rect, 255, 255, 255, 255 * state.flash_at.ease(10, :flip)].solid + end + + # Calls all methods necessary for performing calculations. + def calc + calc_spawn_zombie + calc_move_zombies + calc_player + calc_kill_zombie + end + + # Decreases the zombie spawn countdown by 1 if it has a value greater than 0. + def calc_spawn_zombie + if state.zombie_spawn_countdown > 0 + state.zombie_spawn_countdown -= 1 + return + end + + # New zombies are created, positioned on the screen, and added to the zombies collection. + state.zombies << state.new_entity(:zombie) do |z| # each zombie is declared a new entity + if rand > 0.5 + z.x = grid.rect.w.randomize(:ratio) # random x position on screen (within grid scope) + z.y = [-10, 730].sample # y position is set to either -10 or 730 (randomly chosen) + # the possible values exceed the screen's scope so zombies appear to be coming from far away + else + z.x = [-10, 1290].sample # x position is set to either -10 or 1290 (randomly chosen) + z.y = grid.rect.w.randomize(:ratio) # random y position on screen + end + end + + # Calls random_spawn_countdown method (determines how fast new zombies appear) + state.zombie_spawn_countdown = random_spawn_countdown state.zombie_min_spawn_rate + state.zombie_min_spawn_rate -= 1 + # set to either the current zombie_min_spawn_rate or 0, depending on which value is greater + state.zombie_min_spawn_rate = state.zombie_min_spawn_rate.greater(0) + end + + # Moves all zombies towards the center of the screen. + # All zombies that reach the center (640, 360) are rejected from the zombies collection and disappear. + def calc_move_zombies + state.zombies.each do |z| # for each zombie in the collection + z.y = z.y.towards(360, 0.1) # move the zombie towards the center (640, 360) at a rate of 0.1 + z.x = z.x.towards(640, 0.1) # change 0.1 to 1.1 and see how much faster the zombies move to the center + end + state.zombies = state.zombies.reject { |z| z.y == 360 && z.x == 640 } # remove zombies that are in center + end + + # Calculates the position and movement of the player on the screen. + def calc_player + state.player.x += state.player.dx # changes x based on dx (change in x) + state.player.y += state.player.dy # changes y based on dy (change in y) + + state.player.dx *= 0.9 # scales dx down + state.player.dy *= 0.9 # scales dy down + + # Compares player's x to 1280 to find lesser value, then compares result to 0 to find greater value. + # This ensures that the player remains within the screen's scope. + state.player.x = state.player.x.lesser(1280).greater(0) + state.player.y = state.player.y.lesser(720).greater(0) # same with player's y + end + + # Finds all zombies that intersect with the player's sprite. These zombies are removed from the zombies collection + # and added to the killed_zombies collection since any zombie that intersects with the player is killed. + def calc_kill_zombie + + # Find all zombies that intersect with the player. They are considered killed. + killed_this_frame = state.zombies.find_all { |z| z.sprite.intersect_rect? state.player_sprite } + state.zombies = state.zombies - killed_this_frame # remove newly killed zombies from zombies collection + state.killed_zombies += killed_this_frame # add newly killed zombies to killed zombies + + if killed_this_frame.length > 0 # if atleast one zombie was killed in the frame + state.flash_at = state.tick_count # flash_at set to the frame when the zombie was killed + # Don't forget, the rendered flash lasts for 10 frames after the zombie is killed (look at render_flash method) + end + + # Sets the tick_count (passage of time) as the value of the death_at variable for each killed zombie. + # Death_at stores the frame a zombie was killed. + killed_this_frame.each do |z| + z.death_at = state.tick_count + end + + # Zombies are rejected from the killed_zombies collection depending on when they were killed. + # They are rejected if more than 30 frames have passed since their death. + state.killed_zombies = state.killed_zombies.reject { |z| state.tick_count - z.death_at > 30 } + end + + # Uses input from the user to move the player around the screen. + def input + + # If the "a" key or left key is pressed, the x position of the player decreases. + # Otherwise, if the "d" key or right key is pressed, the x position of the player increases. + if inputs.keyboard.key_held.a || inputs.keyboard.key_held.left + state.player.x -= 5 + elsif inputs.keyboard.key_held.d || inputs.keyboard.key_held.right + state.player.x += 5 + end + + # If the "w" or up key is pressed, the y position of the player increases. + # Otherwise, if the "s" or down key is pressed, the y position of the player decreases. + if inputs.keyboard.key_held.w || inputs.keyboard.key_held.up + state.player.y += 5 + elsif inputs.keyboard.key_held.s || inputs.keyboard.key_held.down + state.player.y -= 5 + end + + # Sets the attack angle so the player can move and attack in the precise direction it wants to go. + # If the mouse is moved, the attack angle is changed (based on the player's position and mouse position). + # Attack angle also contributes to the position of red square. + if inputs.mouse.moved + state.player.attack_angle = inputs.mouse.position.angle_from [state.player.x, state.player.y] + end + + if inputs.mouse.click && state.player.dx < 0.5 && state.player.dy < 0.5 + state.player.attack_angle_on_click = inputs.mouse.position.angle_from [state.player.x, state.player.y] + state.player.attack_angle = state.player.attack_angle_on_click # player's attack angle is set + state.player.dx = state.player.attack_angle.vector_x(25) # change in player's position + state.player.dy = state.player.attack_angle.vector_y(25) + end + end + + # Sets the zombie spawn's countdown to a random number. + # How fast zombies appear (change the 60 to 6 and too many zombies will appear at once!) + def random_spawn_countdown minimum + 10.randomize(:ratio, :sign).to_i + 60 + end + + # Helps to iterate through the images in the sprites folder by setting the animation index. + # 3 frames is how long to show an image, and 6 is how many images to flip through. + def animation_index at + at.idiv(3).mod(6) + end + + # Animates the zombies by using the animation index to go through the images in the sprites folder. + def animation_sprite zombie, at = nil + at ||= zombie.created_at_elapsed # how long it is has been since a zombie was created + index = animation_index at + "sprites/zombie-#{index}.png" # string interpolation to iterate through images + end +end + +$protect_the_puppies_from_the_zombies = ProtectThePuppiesFromTheZombies.new + +def tick args + $protect_the_puppies_from_the_zombies.grid = args.grid + $protect_the_puppies_from_the_zombies.inputs = args.inputs + $protect_the_puppies_from_the_zombies.state = args.state + $protect_the_puppies_from_the_zombies.outputs = args.outputs + $protect_the_puppies_from_the_zombies.tick + tick_instructions args, "How to get the mouse position and translate it to an x, y position using .vector_x and .vector_y. CLICK to play." +end + +def tick_instructions args, text, y = 715 + return if args.state.key_event_occurred + if args.inputs.mouse.click || + args.inputs.keyboard.directional_vector || + args.inputs.keyboard.key_down.enter || + args.inputs.keyboard.key_down.escape + args.state.key_event_occurred = true + end + + args.outputs.debug << [0, y - 50, 1280, 60].solid + args.outputs.debug << [640, y, text, 1, 1, 255, 255, 255].label + args.outputs.debug << [640, y - 25, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label +end |
