Reap and tear




Have you ever thought, why a goose steals humans stuff?
Maybe he is preparing for eternal battle with forces of evil?
Titled Goose Team present a brand new game about goose guy - HONK.
HONK is a true story behind strange behaviour (from humans perspective) of a goose. And without any lootboxes!
When I started working on HONK I wanted to make a real singleplayer experience. However, I realized, I couldn't deliver good results in such short time in such a tiny team (an artist and I). Nonethe less, I had made a level with doors (which enemies can open) and spawn system.

Because the game doesn't clean a surface, all the blood is kept on the level. After several playthroughs, the floor of the arena takes after a piece of abstract art... Art of reap and tear!

Gif preview is always better than just screenshots, but not many people know what sofware they should use for this.
For my game HONK I use 2 things: 1. Screentogif, as a gif recorder and as an editor; 2. Build in gif recorder in Game Maker Studio 2;
The difference is that GM2 gif recorder doesn't record UI, that's why I also use Screentogif.
Using both methotds or only screentogif can help you make a lot of cool gifs for your game.


Now you can download a post-jam version of HONK.
There aren't many changes: 1. You can see your time and score on the End screen. 2. Blood now is less vivid. 3. Some sounds are less loud.
Here is my personal record!

Here is some information about movement system in HONK which I'll use in my another project One Spear, One Kill.
First of all, it based on this tutorial - A Comprehensive Guide To Time Dilation. Even though there is an example project, I've written my own system, which I use everywhere in the game.
If I need to move something with acceleration or deceleration I will add in Step Event these scripts: 1. MoveObject; 2. CalculateAcceleratedVelocity; 3. CalculateDeceleratedVelocity; 4. CalculateConstantMovement;
It's a straightforward script, but because of using this system everywhere wherever I want, I keep it so simple. As different things require different calculations. For instance, I use both CalculateAcceleratedVelocity and CalculateDeceleratedVelocity in the player object; on the other hand, in projectiles, I use only one of these things at the same time.
It's very important to call this script before velocity calculations.
```gml //------------// // MoveObject // //------------//
// @param isConstant
var _isConstant = argument0;
if (_isConstant) { x += velocityX * global.TimeFactor; y += velocityY * global.TimeFactor; } else { x += velocityX; y += velocityY; } ```
This script calculates velocity while an object is accelerating.
``` //------------------------------// // CalculateAcceleratedVelocity // //------------------------------//
/// @param direction /// @param velocityCurrent /// @param velocityMax /// @param acceleration
var _targetDirection = argument0; var _velocityCurrent = argument1; var _velocityMax = argument2; var _acceleration = argument3;
//Delta X and Y calculations var positionDelta = global.TimeFactor * _velocityCurrent + (0.5 * sqr(global.TimeFactor) * _acceleration); _velocityCurrent = min(velocityCurrent + acceleration * global.TimeFactor, _velocityMax); velocityX = lengthdirx(positionDelta, _targetDirection); velocityY = lengthdiry(_positionDelta, _targetDirection);
return _velocityCurrent; ```
This script calculates velocity while an object is decelerating.
``` //------------------------------// // CalculateDeceleratedVelocity // //------------------------------//
/// @param direction /// @param velocityCurrent /// @param friction
var _targetDirection = argument0; var _velocityCurrent = argument1; var _friction = argument2;
var addX = _friction * sign(velocityCurrent); var positionDelta = global.TimeFactor * _velocityCurrent + (0.5 * sqr(global.TimeFactor) * _addX); _velocityCurrent = max(velocityCurrent - friction * global.TimeFactor, 0); velocityX = lengthdirx(positionDelta, _targetDirection); velocityY = lengthdiry(_positionDelta, _targetDirection);
return _velocityCurrent; ```
In case when I need to move something with a constant velocity I use this script.
``` //---------------------------// // CalculateConstantVelocity // //---------------------------//
/// @param Velocity /// @param direction
var _velocity = argument0; var _direction = argument1;
velocityX = lengthdirx(velocity, direction); velocityY = lengthdiry(_velocity, _direction); ```
And what about collisions? I use a classic script for checking collisions, and I place it after all movement calculations.
But be aware, I don't use this script to execute other code, e.g. destroying projectiles, dealing damage etc.
``` //----------------// // CheckCollision // //----------------//
// @param object
var _object = argument0;
// Collision X if (placemeeting(x + velocityX, y, _object)) { while (!placemeeting(x + sign(velocityX), y, _object)) { x += sign(velocityX); } velocityX = 0; }
// Collision Y if (placemeeting(x, y + velocityY, _object)) { while (!placemeeting(x, y + sign(velocityY), _object)) { y += sign(velocityY); } velocityY = 0; } ```
Thank you for reading, feel free to ask questions and give feedback.
I've just made a small post-jam update of HONK with better camera, damage feedback and an epic death sequence!

Here is a short tutorial about bloody effect in HONK.

First of all, this effect is based on this tutorial https://www.youtube.com/watch?v=nz8FUMHEyAU
However, I've made some slight changes to make it better for my game. I think, that my approach is still clunky and needs some upgrade in the future.
I use my movement system in this effect, I highly recommend to read about it. Movement.
In my game I use special object which is called obj_drawer, this object does one job - sorting sprites and drawing them in the order I need. It takes a lot of time to explain this, now you should create an empty object which will control surface for these bloody effects.
Create event
surfaceFloorEffects= surface_create(room_width, room_height);
Draw event
if (surface_exists(surfaceFloorEffects))
{
draw_surface_ext(surfaceFloorEffects, 0, 0, 1.0, 1.0, 0, c_white, 1.0);
}
else
{
surfaceFloorEffects = surface_create(room_width, room_height);
}
Clear event
surface_free(surfaceFloorEffects);
In the project I use special base objects. They include some logic, which is shared by other objects, e.g. I have obj_sortable, which contain logic for sorting etc.
In this case this you need to create obj_floorEffect and place it on your level.
Destroy event ``` var surface = objdrawer.surfaceFloorEffects;
if (surfaceexists(surface)) { surfacesettarget(_surface);
draw_sprite_ext(sprite_index, image_index, x, y, image_xscale, image_yscale, direction, c_gray, image_alpha);
surface_reset_target();
} else { surface = surfacecreate(roomwidth, roomheight); } ```
Create object vfx_splatter without any sprite.
Variable definitions
You need this variables to adjust child objects.

Create event ``` velocityMax = 20;
// Visual paarameters imagespeed = 0; imageindex = irandomrange(0, imagenumber - 1); imagexscale = randomrange(0.05, 1); imageyscale = randomrange(0.05, 1); imagealpha = randomrange(0.25, 1);
// Movement parameters direction = randomrange(0, 359); imageangle = direction; velocityCurrent = randomrange(0.4, velocityMax); groundFriction = randomrange(0.1, 0.2); velocityX = 0; velocityY = 0;
// Smear parameters
smearTime = 3;
smearTimer = 0;
**Step event**
// Movement
MoveObject();
velocityCurrent = CalculateDeceleratedVelocity(direction, velocityCurrent, groundFriction);
if (velocityCurrent <= 0) { instance_destroy() }
// Smearing smearTimer += global.TimeFactor;
var checkTimer = checktimer(smearTimer, smearTime);
if (_checkTimer) { smearTimer = 0;
instance_create_layer(x, y, layer, smearObject);
} ``` check_timer script
Be aware, I use timefactor, it means you need to arrange global.TimeFactor somewhere at the very start of the game.
gml
global.TimeFactor = 1;
``` /// @param timer /// @param time
var _timer = argument0; var _time = argument1;
return (floor(timer) >= _time && floor(timer - global.TimeFactor) != floor(_timer)); ```
Create object vfxsmear. It must not have any sprite as well as vfxsplatter.
Create event ``` imagespeed = 0; imageindex = irandomrange(0, imagenumber - 1); imagexscale = randomrange(0.05, 0.5); imageyscale = randomrange(0.05, 0.5); imageangle = irandomrange(0, 359); imagealpha = randomrange(0.25, 1);
instance_destroy(); ```
Here is an example of blood sprite.

Yep, it’s small.
After this, you need to create a new object for splatter and smear and make their parents vfxsplatter and vfxsmear relatively.
Do not forget to set up variable definitions.
This script will definitely make adjustments of the effect easier.
``` /// @param x /// @param y /// @param splatterObject /// @param count /// @param velocityMax
var _x = argument0; var _y = argument1; var _object = argument2; var _count = argument3; var _velocity = argument4;
for (var i = 0; i < count; i++) { var _splatter = instancecreatelayer(x, _y, layer, _object);
with (_splatter)
{
velocityMax = _velocity;
velocityCurrent = random_range(0.4, velocityMax);
}
} ```
Now you can place this script wherever you want, but be careful when you will use it in step events.
Now you can reap and tear in your game with ease!
I have a strong notion, a game should always have gamepad and keyboard controls and a player shouldn't spend a lot of time on switching between them.
That's why I've made a simple input method manager which I used in HONK. You should place this object in all rooms and it will change your input method on the go.
``` // Create a list of gamepad slots connectedGamepads = dslistcreate();
// Set active gamepad global.ActiveGamepad = noone;
// Setup input methods enum InputMethod { Gamepad, KeyboardMouse }
// Set current input global.CurrentInput = InputMethod.KeyboardMouse;
// Set deadzone and threshhold for Xbox gamepad xboxAxisDeadzone = 0.25; xboxButtonThreshhold = 0.1;
// Set deadzone and threshhold for PS4 gamepad ps4AxisDeadzone = 0.2; ps4ButtonThreshhold = 0.1; ```
ds_list_destroy(connectedGamepads);
``` /// @description InputSwitcher
var listSize = dslist_size(connectedGamepads);
if (_listSize != 0) { for (var i = 0; i < _listSize; i++) { var _slot = connectedGamepads[| i];
var _leftStickV = gamepad_axis_value(_slot, gp_axislv) != 0;
var _leftStickH = gamepad_axis_value(_slot, gp_axislh) != 0;
var _rightStickV = gamepad_axis_value(_slot, gp_axisrv) != 0;
var _rightStickH = gamepad_axis_value(_slot, gp_axisrh) != 0;
for (var k = gp_face1; k < gp_axisrv; k++)
{
if (gamepad_button_check_pressed(_slot, k))
{
global.CurrentInput = InputMethod.Gamepad;
set_active_gamepad(_slot);
}
}
if (_leftStickV && _leftStickH) || (_rightStickV && _rightStickH)
{
global.CurrentInput = InputMethod.Gamepad;
set_active_gamepad(_slot);
}
}
if (global.CurrentInput != InputMethod.KeyboardMouse)
{
var _mouseKeyPressed = mouse_check_button_pressed(mb_any);
var _keyboardKeyPressed = keyboard_check_pressed(vk_anykey);
if (_mouseKeyPressed || _keyboardKeyPressed)
{
global.CurrentInput = InputMethod.KeyboardMouse;
}
}
} ```
``` /// @description GamepadConnectionHandler
switch(asyncload[? "eventtype"])
{
case "gamepad discovered":
var gamepadSlot = asyncload[? "pad_index"];
if (gamepad_is_supported())
{
ds_list_add(connectedGamepads, _gamepadSlot);
set_active_gamepad(_gamepadSlot);
global.CurrentInput = InputMethod.Gamepad;
}
break;
case "gamepad lost":
var _gamepadSlot = async_load[? "pad_index"];
var _gamepadListIndex = ds_list_find_index(connectedGamepads, _gamepadSlot);
ds_list_delete(connectedGamepads, _gamepadListIndex);
var _listSize = ds_list_size(connectedGamepads);
if (_listSize == 0)
{
global.ActiveGamepad = noone;
global.CurrentInput = InputMethod.KeyboardMouse;
}
else if (_gamepadSlot == global.ActiveGamepad)
{
global.ActiveGamepad = connectedGamepads[| 0];
}
break;
} ```
``` // @param gamepadSlot
var _gamepadSlot = argument0;
global.ActiveGamepad = gamepadSlot; if (gamepadSlot >= 0 && gamepadSlot <= 3) // Set dead zones for Xbox controller { gamepadsetaxisdeadzone(gamepadSlot, xboxAxisDeadzone); gamepadsetbuttonthreshold(gamepadSlot, xboxButtonThreshhold); } else // Set dead zones for Playstation controller { gamepadsetaxisdeadzone(gamepadSlot, ps4AxisDeadzone); gamepadsetbuttonthreshold(_gamepadSlot, ps4ButtonThreshhold); } ```
I didn't use this code in HONK but it helps me to make this flexible camera for Game Maker 2.
Here is an original devlog.
Hello everybody, I'm carrying on working on the update. Recently I finished developing a new camera system which is very flexible and easy to use.
However, before starting working on the camera system, I had made a pixel perfect display manager.
It based on a Game resolution tutorial series by PixelatedPope:
I highly recommend checking this tutorial because it's superb and explains a lot of important moments.
And here is my version of display manager. There aren't significant differences with display manager by PixelatedPope, instead of variables names.
After implementing a display manager, I started working on a new camera. An old version was pretty simple and not so flexible as I wanted.
I use this tutorial by FriendlyCosmonaut as a start point. This tutorial is fantastic, as it shows how to make a flexible camera controller which can be useful everywhere. Nonetheless, I've made some changes to make this camera system a little bit better for my project.
I wanted to make smooth camera for some camera modes; I needed game pad support; Let's dive in it!
``` /// @description Camera parameters
// Main settings global.Camera = id;
// Macroses
// User events
// Transform cameraX = x; cameraY = y;
cameraOriginX = cameraWidth * 0.5; cameraOriginY = cameraHeight * 0.5;
// Cameramodes enum CameraMode { FollowObject, FollowBorder, FollowPointPeek, FollowDrag, MoveToTarget, MoveToFollowObject }
cameraMode = CameraMode.MoveToTarget; clampToBorders = false;
// Follow parameters cameraFollowTarget = objplayer; targetX = roomwidth / 2; targetY = room_height / 2; isSmooth = true;
mousePreviousX = -1; mousePreviousY = -1;
cameraButtonMoveSpeed = 5; // Only for gamepad and keyboard controls cameraDragSpeed = 0.5; // Only for CameraMode.FollowDrag cameraSpeed = 0.1;
// Camera shake parameters cameraShakeValue = 0; angularShakeEnabled = false; // Enables angular shaking
// Zoom parameters cameraZoom = 0.65; cameraZoomMax = 4; ```
This is a state machine of the camera. You can easily modify it without any problems because all logic of each mode contains in separate user events.
``` /// @description Camera logic
cameraOriginX = cameraWidth * 0.5; cameraOriginY = cameraHeight * 0.5;
cameraX = cameraPositionX; cameraY = cameraPositionY;
switch (cameraMode) { case CameraMode.FollowObject: ExecuteFollowObject; break;
case CameraMode.FollowBorder:
ExecuteFollowBorder;
break;
case CameraMode.FollowPointPeek:
ExecuteFollowPointPeek;
break;
case CameraMode.FollowDrag:
ExecuteFollowDrag;
break;
case CameraMode.MoveToTarget:
ExecuteMoveToTarget;
break;
case CameraMode.MoveToFollowObject:
ExecuteMoveToFollowObject;
break;
}
ClampCameraPosition;
ExecuteCameraShake;
camerasetview_pos(mainCamera, cameraX, cameraY); ```
Why do I use user_events? I don't like creating a lot of exclusive scripts for one object, and user events are a good place to avoid this problem and store exclusive code for objects.
Secondly, it's really easy to make changes in user event than in all sequence, in this case, I'm sure that I won't break something else because of my inattentiveness.
``` ///----------------------------------------------/// /// User Event 0 /// ///----------------------------------------------///
/// @description FollowObject
var targetExists = instanceexists(cameraFollowTarget);
if (_targetExists) { targetX = cameraFollowTarget.x; targetY = cameraFollowTarget.y;
CalculateCameraDelayMovement();
}
///----------------------------------------------/// /// User Event 1 /// ///----------------------------------------------///
/// @description FollowBorder
switch (global.CurrentInput) { case InputMethod.KeyboardMouse: var _borderStartMargin = 0.35; var _borderEndMargin = 1 - _borderStartMargin;
var _borderStartX = cameraX + (cameraWidth * _borderStartMargin);
var _borderStartY = cameraY + (cameraHeight * _borderStartMargin);
var _borderEndX = cameraX + (cameraWidth * _borderEndMargin);
var _borderEndY = cameraY + (cameraHeight * _borderEndMargin);
var _isInsideBorder = point_in_rectangle(mouse_x, mouse_y, _borderStartX, _borderStartY, _borderEndX, _borderEndY);
if (!_isInsideBorder)
{
var _lerpAlpha = 0.01;
cameraX = lerp(cameraX, mouse_x - cameraOriginX, _lerpAlpha);
cameraY = lerp(cameraY, mouse_y - cameraOriginY, _lerpAlpha);
}
else
{
ExecuteMoveWithKeyboard;
}
break;
case InputMethod.Gamepad:
ExecuteMoveWithGamepad;
break;
}
///----------------------------------------------/// /// User Event 2 /// ///----------------------------------------------///
/// @description FollowPointPeek
var _distanceMax = 190; var _startPointX = cameraFollowTarget.x; var _startPointY = cameraFollowTarget.y - cameraFollowTarget.offsetY - cameraFollowTarget.z;
switch (global.CurrentInput) { case InputMethod.KeyboardMouse: var direction = pointdirection(startPointX, _startPointY, mousex, mousey); var _aimDistance = pointdistance(startPointX, _startPointY, mousex, mousey); var _distanceAlpha = min(aimDistance / _distanceMax, 1); break;
case InputMethod.Gamepad:
var _axisH = gamepad_axis_value(global.ActiveGamepad, gp_axisrh);
var _axisV = gamepad_axis_value(global.ActiveGamepad, gp_axisrv);
var _direction = point_direction(0, 0, _axisH, _axisV);
var _distanceAlpha = min(point_distance(0, 0, _axisH, _axisV), 1);
break;
}
var distance = lerp(0, _distanceMax, _distanceAlpha); var _endPointX = _startPointX + lengthdirx(distance, _direction) var _endPointY = _startPointY + lengthdiry(_distance, _direction)
targetX = lerp(startPointX, _endPointX, 0.2); targetY = lerp(startPointY, _endPointY, 0.2);
CalculateCameraDelayMovement();
///----------------------------------------------/// /// User Event 3 /// ///----------------------------------------------///
/// @description FollowDrag
switch (global.CurrentInput) { case InputMethod.KeyboardMouse: var mouseClick = mousecheckbutton(mbright);
var _mouseX = display_mouse_get_x();
var _mouseY = display_mouse_get_y();
if (_mouseClick)
{
cameraX += (mousePreviousX - _mouseX) * cameraDragSpeed;
cameraY += (mousePreviousY - _mouseY) * cameraDragSpeed;
}
else
{
ExecuteMoveWithKeyboard;
}
mousePreviousX = _mouseX;
mousePreviousY = _mouseY;
break;
case InputMethod.Gamepad:
ExecuteMoveWithGamepad;
break;
}
///----------------------------------------------/// /// User Event 4 /// ///----------------------------------------------///
/// @description MoveToTarget
MoveCameraToPoint(cameraSpeed);
///----------------------------------------------/// /// User Event 5 /// ///----------------------------------------------///
/// @description MoveToFollowObject
var targetExists = instanceexists(cameraFollowTarget);
if (_targetExists) { targetX = cameraFollowTarget.x; targetY = cameraFollowTarget.y;
MoveCameraToPoint(cameraSpeed);
var _distance = point_distance(cameraX, cameraY, targetX - cameraOriginX, targetY - cameraOriginY);
if (_distance < 1)
{
cameraMode = CameraMode.FollowObject;
}
}
///----------------------------------------------/// /// User Event 6 /// ///----------------------------------------------///
/// @description MoveWithGamepad
var axisH = gamepadaxisvalue(global.ActiveGamepad, gpaxisrh); var axisV = gamepadaxisvalue(global.ActiveGamepad, gpaxisrv);
var direction = pointdirection(0, 0, axisH, _axisV); var _lerpAlpha = min(pointdistance(0, 0, _axisH, _axisV), 1); var _speed = lerp(0, cameraButtonMoveSpeed, _lerpAlpha);
cameraX += lengthdirx(speed, direction); cameraY += lengthdiry(_speed, _direction);
///----------------------------------------------/// /// User Event 7 /// ///----------------------------------------------///
/// @description MoveWithKeyboard
var directionX = objgameManager.keyMoveRight - objgameManager.keyMoveLeft; var _directionY = objgameManager.keyMoveDown - obj_gameManager.keyMoveUp;
if (directionX != 0 || _directionY != 0) { var _direction = pointdirection(0, 0, _directionX, _directionY);
var _speedX = lengthdir_x(cameraButtonMoveSpeed, _direction);
var _speedY = lengthdir_y(cameraButtonMoveSpeed, _direction);
cameraX += _speedX;
cameraY += _speedY;
}
///----------------------------------------------/// /// User Event 8 /// ///----------------------------------------------///
/// @description ClampCameraPosition
if (clampToBorders) { cameraX = clamp(cameraX, 0, roomwidth - cameraWidth); cameraY = clamp(cameraY, 0, roomheight - cameraHeight); }
///----------------------------------------------/// /// User Event 9 /// ///----------------------------------------------///
/// @description CameraShaker
// Private parameters var _cameraShakePower = 5; var _cameraShakeDrop = 0.1; var _cameraAngularShakePower = 0.5;
// Shake range calculations var _shakeRange = power(cameraShakeValue, 2) * _cameraShakePower;
// Add shakeRange to camera position cameraX += randomrange(-shakeRange, _shakeRange); cameraY += randomrange(-_shakeRange, _shakeRange);
// Chanege view angle to shake camera angle if angularShakeEnabled { camerasetviewangle(mainCamera, randomrange(-_shakeRange, _shakeRange) * _cameraAngularShakePower); }
// Decrease shake value if cameraShakeValue > 0 { cameraShakeValue = max(cameraShakeValue - _cameraShakeDrop, 0); } ```
In order to make my life a little bit easier, I use some scripts too. But be aware CalculateCameraDelayMovement and MoveCameraToPoint are used exclusively in camera code.
You should use SetCameraMode to change camera mode and SetCameraZoom to change camera zoom.
``` ///----------------------------------------------/// /// CalculateCameraDelayMovement /// ///----------------------------------------------///
var _x = targetX - cameraOriginX; var _y = targetY - cameraOriginY;
if (isSmooth) { var _followSpeed = 0.08;
cameraX = lerp(cameraX, _x, _followSpeed);
cameraY = lerp(cameraY, _y, _followSpeed);
} else { cameraX = _x; cameraY = _y; }
///----------------------------------------------/// /// MoveCameraToPoint /// ///----------------------------------------------///
/// @param moveSpeed
var _moveSpeed = argument0;
cameraX = lerp(cameraX, targetX - cameraOriginX, _moveSpeed); cameraY = lerp(cameraY, targetY - cameraOriginY, _moveSpeed);
///----------------------------------------------/// /// SetCameraMode /// ///----------------------------------------------///
/// @description SetCameraMode
/// @param mode /// @param followTarget/targetX /// @param targetY
with (global.Camera) { cameraMode = argument[0];
switch (cameraMode)
{
case CameraMode.FollowObject:
case CameraMode.MoveToFollowObject:
cameraFollowTarget = argument[1];
break;
case CameraMode.MoveToTarget:
targetX = argument[1];
targetY = argument[2];
break;
}
}
///----------------------------------------------/// /// SetCameraZoom /// ///----------------------------------------------///
/// @description SetCameraZoom
/// @param newZoom
var _zoom = argument0;
with (global.Camera) { cameraZoom = clamp(zoom, 0.1, cameraZoomMax); camerasetviewsize(mainCamera, global.IdealWidth / cameraZoom, global.IdealHeight / cameraZoom); } ```
Thank you for reading!
While Doom guy preparing for his eternal battle, goose guy is defending our planet from the hordes of evil beings with his shotgun in HONK.
https://www.youtube.com/watch?v=jC4WCiyWu8Q
I'm happy to see how many people have played HONK so far.
Despite hating the theme of this jam, I'm happy to participate in the jam, and play so many magnificent games. Thank you all and wish you luck with your ratings!

Maybe HONK didn't get a high rating, but its rating is better than the rating of Ludum Dare 44 game.
HONK is my second solo programmed game for game jams and my second complete game with GameMaker Studio 2.
There are a lot of things I have to learn, next time I'll try to make a better game. Probaly, HONK II, or HONK Ultimate, HONK Eternal.

Anyway, rihgt now I'm working on a new version of the game which I've made for GMTK 2019 jam and post various staff about this process on my twitter page. I appretiate any feedback and discussions!


I had planned to be a solo jammer, but suddenly I've found a team mate! And it means that Titled Goose Team assembles again!!
