• Andrew Jonhardt

Mask of Undying - The Arm

Per my last post, this blog entry will be covering my implementation of the main character's attacking limb in Mask of Undying. Technically he attacks with a punch, but I've been calling it "the arm" since this project first started so I'm sticking with that for consistency.


extends Area2D

#parent direction
var right
var left
var up
var down
var punch
#position-impacting variables
var offset = 24
var knife_offset = 16
var board_base_off = 24
var board_comb_off = 20
var p_time
var ori_p_time = 10
var punched = false
#children (items)
var knife
var board

Implementing attacking limbs has always seemed oddly difficult to me, and my list of attack-affecting variables reflects this. It's probably just me.


Now, as you can probably tell from the above, the Arm is just an Area2D. This means it is incapable of pushing anything without such interactions being manually programmed, and that the arm will pass through walls and other "solid" objects. It's true that you can use a kinematic2D body instead, because a kinematic object will still report collisions and will be stopped by solid objects. However, I've also taken the step of nesting the Arm within the player object.

In order to avoid physics complications, and to ensure an easy setup, I believe manually programming all Arm interactions will be preferable for the long term.


func _ready():
	p_time = ori_p_time

#convert inputs in parent to actable bools
func get_parent_var():
	right = get_parent().right
	left = get_parent().left
	up = get_parent().up
	down = get_parent().down
	punch = get_parent().punch

The _ready function sets up a timer, and the get_parent_var function works to ensure the direction the Player is facing is always tracked and passed to the Arm.


func item_correction():
	#this function corrects knife and board pos when weapons should be active
	if knife == null:
		return
	else:
		#the below mess repositions the knife when ya punch
		if punch:
			if right and !left and !down and !up:
				knife.position.x = knife_offset #modify X despite rotation + 16 because of adding
				#onto hand's pos.
				if knife.rotation_degrees != 90: #should be able to use 1 rotation for
					#left and right
					knife.rotation_degrees = 90
			elif left and !down and !up and !right:
				knife.position.x = -knife_offset #modify X despite rotation - 16 because of adding
				#onto hand's pos.
				if knife.rotation_degrees != 90:
					knife.rotation_degrees = 90
			elif up and !left and !down and !right:
				knife.position.y = -knife_offset
				if knife.rotation_degrees != 0:
					knife.rotation_degrees = 0
			elif down and !left and !up and !right:
				knife.position.y = knife_offset
				if knife.rotation_degrees != 0:
					knife.rotation_degrees = 0
			elif left and up and !down and !right:
				knife.position.y = -knife_offset
				knife.position.x = -knife_offset
				if knife.rotation_degrees != -45:
					knife.rotation_degrees = -45
			elif left and down and !up and !right:
				knife.position.y = knife_offset
				knife.position.x = -knife_offset
				if knife.rotation_degrees != 45:
					knife.rotation_degrees = 45
			elif right and up and !down and !left:
				knife.position.y = -knife_offset
				knife.position.x = knife_offset
				if knife.rotation_degrees != 45:
					knife.rotation_degrees = 45
			elif right and down and !up and !left:
				knife.position.y = knife_offset
				knife.position.x = knife_offset
				if knife.rotation_degrees != -45:
					knife.rotation_degrees = -45
		else:
			knife.rotation_degrees = 0
			knife.position = Vector2.ZERO
	if board == null:
		return
	else:
		if punch:
			if right and !left and !down and !up:
				board.position.x = board_base_off #modify X despite rotation + 16 because of adding
				#onto hand's pos.
				if board.rotation_degrees != 90: #should be able to use 1 rotation for
					#left and right
					board.rotation_degrees = 90
			elif left and !down and !up and !right:
				board.position.x = -board_base_off #modify X despite rotation - 16 because of adding
				#onto hand's pos.
				if board.rotation_degrees != 90:
					board.rotation_degrees = 90
			elif up and !left and !down and !right:
				board.position.y = -board_base_off
				if board.rotation_degrees != 0:
					board.rotation_degrees = 0
			elif down and !left and !up and !right:
				board.position.y = board_base_off
				if board.rotation_degrees != 0:
					board.rotation_degrees = 0
			elif left and up and !down and !right:
				board.position.y = -board_comb_off
				board.position.x = -board_comb_off
				if board.rotation_degrees != -45:
					board.rotation_degrees = -45
			elif left and down and !up and !right:
				board.position.y = board_comb_off
				board.position.x = -board_comb_off
				if board.rotation_degrees != 45:
					board.rotation_degrees = 45
			elif right and up and !down and !left:
				board.position.y = -board_comb_off
				board.position.x = board_comb_off
				if board.rotation_degrees != 45:
					board.rotation_degrees = 45
			elif right and down and !up and !left:
				board.position.y = board_comb_off
				board.position.x = board_comb_off
				if board.rotation_degrees != -45:
					board.rotation_degrees = -45
		else:
			board.rotation_degrees = 0
			board.position = Vector2.ZERO


The item_correction function was alot of fun to work out, because it signifies my first attempt to integrate items into Mask of Undying. Essentially, if the player has a knife, the above function will simply stick the knife at the end of the Arm, no matter what direction the player is currently facing. Think of it like a knife thrust instead of a knife swipe.


At the moment, the board behaves exactly like the knife. Eventually, I'll need to fix this so that the board is swung instead. For now, through, I preferred to confirm that I could get both appearing ingame.


#move attack out of player body to deal damage
func move_arm(delta):
	if punch: #true as soon as button is pressed. Not set to false in parent script.
		p_time -= 1 #timer for how long punch hangs out
		if right:
			self.position.x = offset
		if left:
			self.position.x = -offset
		if up:
			self.position.y = -offset
		if down:
			self.position.y = offset
	else:
		self.position = Vector2.ZERO
	if p_time <= 0:
		get_parent().punch = false #simply using the variable isn't enough to
		#modify the parent. Must re-call to parent.
		p_time = ori_p_time
		get_parent().fist.visible = false

The move_arm function does something that is probably a little silly; It reaches back into the parent script and fiddles with the variable that it's using to activate itself. I've ensured the boolean variable "punch" can only be switched on by the player, and that the Arm script has full control of when the boolean is switched off, but this still strikes me as a potential issue source if I were to share this project with anyone else.


One thing that I'm proud of is how much smaller the above function is versus some of my earlier punching scripts. To be fair, the result is more game-y looking than ever, and I haven't implemented a rotation for the actual Arm sprite yet. However, rotating and positioning the Arm was one of the first things I dealt with in my last version, so I'm not worried about it.

If you're curious about the Arm flashing, that's controlled by the Player script and is covered in my last post.

func _physics_process(delta):
	item_correction()
	get_parent_var()
	move_arm(delta)

Finally, the physics_process brings it all together. Pretty simple stuff. I'm happy with it, though.


For my next post (the 19th) I'll be covering A* pathfinding. Why? Well, after a great deal of thought, I'm convinced it's what Mask needs. Additionally, I have a working implementation. I don't have A* completely figured out, unfortunately, but I do have a basic understanding of the the Godot implementation.


Until next week!

© 2023 by Andrew Jonhardt. Proudly created with Wix.com