Mask of Undying update #1
I'm happy to announce that Mask is again my main project. It's essentially been half a year since I put serious effort into the project, and the Godot game engine has been updated to v3.2, so I've decided to use what I learned from my experiments (mainly Python tutorials and the unnamed 2D platformer I was messing around with) to rebuild Mask. At the same time, I'm hoping to re-use scripts and techniques that work "good enough" to ensure I can actually complete Mask this year.
For this first post, I'll be going over the current state of the player script, begging of course with the variables (any line that begins with # is a comment, and thus doesn't impact code at all; it's just a note to myself):
extends KinematicBody2D #input var right = false var left = false var up = false var down = false var dodge = false var punch = false #player movement var velocity = Vector2() var dodge_timer #had to set dodge var up here for access by input (easiest solution) var ori_dodge_timer = 8 #items onready var knife = $Arm/Knife onready var board = $Arm/Board
One of the most interesting things I stumbled across through my experimentation was the above method of converting player inputs (left, right, etc) into variables: by making them booleans. I'll show you how I did this below. It's got to be possible to expand from booleans into strings, dictionaries, etc, which I will be trying in the future. For now, however, I'm just using booleans cause it's what I know.
One other thing to note from the above variables: I'm planning to use pickups, much like Splatterhouse, and both the Knife and Board (as in, nail-filled 2x4) pickups are just children of the player object. So far, this has made implementation pretty easy.
func _ready(): if Global.cur_item == null: #allow for carrying between scenes if not null? Global.cur_item = Global.items.none # Global.cur_item = Global.items.board #purely for testing board.set_process(false) knife.set_process(false) $Arm.knife = knife $Arm.board = board dodge_timer = ori_dodge_timer
The _ready() function above, or the function that only fires when the a scene/area-of-the-game first loads, holds variables pointing to an entirely different script labeled Global. Per the name, Global is a script that is active for as long as the game is running, it's not changed when the player changes into different scenes (most scripts reset), and it can been seen or referenced by every other object in the game.
At the moment, the only variables Global includes are cur_item and the enum (uneditable list) items. The Global script has no functions in it, so it's up to the player to make sure cur_item is set to be item.none. However, any time the player "picks up" (runs into) an item in a level, cur_item will be set to items.heart, items.knife, or items.board, depending on the item. This will be shown further down.
func _process(delta): resolve_item()
I've got a lone _process(delta) function running because most of the interactions I've currently got are based on physics. Resolving items, or changing the player in accordance with when an item is picked up, doesn't matter to me on a physics level, so I split it out. As far as I'm aware, Godot shouldn't have a problem with this.
func resolve_item(): match Global.cur_item: Global.items.none: return Global.items.heart: print("heart!") Global.cur_item = Global.items.none Global.items.knife: print("knife!") knife.visible = true knife.set_process(true) Global.cur_item = Global.items.none Global.items.board: print("board!") board.visible = true board.set_process(true) Global.cur_item = Global.items.none
Above, is my current solution for handling items. It's not complete or fully tested; I've got nothing set for the heart item, as I haven't set up health yet, and nothing is disabling the board or knife items once they've been activated.
func player_input(): #get player inputs if Input.is_action_pressed("ui_right"): right = true else: right = false if Input.is_action_pressed("ui_left"): left = true else: left = false if Input.is_action_pressed("ui_up"): up = true else: up = false if Input.is_action_pressed("ui_down"): down = true else: down = false if Input.is_action_pressed("ui_dodge"): if velocity == Vector2.ZERO: #prevents the player from locking self #in place when not moving, & allows for the player to dodge when #otherwise not moving pass else: dodge = true else: dodge = false dodge_timer = ori_dodge_timer if Input.is_action_just_pressed("ui_accept"): #space punch = true
Ah, and now the player input. It was a journey getting all this to work, but in the interests of everyone's time I'll just break down what's going on: player inputs are turning on booleans, while removing inputs are turning off booleans. There's some timers impacting these booleans as well, further down the script.
I tried using the standard _input(event) all the pros seem to like so much, but I couldn't easily get what I wanted done without adding more complexity.
func action(delta): #speed variables var speed = 3000 var dodge_speed = 60000 #modify velocity to set direction and speed if !dodge: velocity = Vector2((-int(left) + int(right))*speed*delta, (-int(up) + int(down))*speed*delta) elif dodge: #handle dodge movement and timer print(float(dodge_timer)) dodge_timer -= 1 if dodge_timer >0: velocity = Vector2((-int(left) + int(right))*dodge_speed*delta, (-int(up) + int(down))*dodge_speed*delta) elif dodge_timer <= 0: dodge = false velocity = Vector2.ZERO
The above function, per the name, is where all the action happens. The booleans the player is triggering are put into scripts that alter which direction the player is moving (of a possible 8). I put the speed-impacting variables in this function, instead of at the top of the script, as there's nothing speed-related anywhere else in the script.
In my original plan for this project, I intended for the player to have a moment of invulnerability when they dodge. This could still be added later. However, I've come to like the idea of the dodge as just a brief speed boost. You can still be damaged if you're not careful, and there's nothing to stop you from attacking while dodging. Maybe this is sloppy, but it's good enough for now.
func _physics_process(delta): player_input() action(delta) velocity = move_and_slide(velocity)
Finally, we have the _physics_process(delta) function, where everything physics related is executed (movement/attacking/dodging) every frame of the game.
Now, if you've read my blog before, you're probably wondering where the function is that actually handles punching. Well, I'm happy to report that I've successfully split off the punching logic into it's own script (aside from the little that is referenced here), and I'll be going over all that the 1st Sunday of next month.