I contributed most Blueprint code to levels 1 & 2 of Impossible Rescue. This includes level Blueprinting, but also creation of player movement, weapons, abilities, objectives, and enemies. This article will delve into several technical design challenges encountered during Impossible Rescue's development.
An Innovative Solution for Accurate Aiming
Aiming proved a tricky beast to tame. For the Impossible Rescue prototype, I’d implemented a relatively basic aiming functionality. Whenever the mouse was moved, Blueprint code generated a hit location directly under the cursor, then positioned the crosshair 100uu (Unreal units) above this point so that it appeared at the same height as the player’s gun.
There were two faults with this, however. The first (Problem A) was that if the player moused-over a wall or other object, the crosshair would be moved 100uu above that object and the player would end up firing into the sky. The second (Problem B) was that even when aiming at ground level, there was a slight difference between where the mouse cursor was aiming and where the crosshair was rendered.
Problem A was easy to diagnose, but Problem B proved difficult. In the end, I found a similar issue being discussed on Reddit and was able to better understand the problem.
Left: A screenshow showing the cursor (cross) not lining up with the crosshair (white circle).
Right: A diagram of the problem. The crosshair hovers 100uu above the hit location, but it should line up where the red and blue lines cross.
I began trialling solutions, first attempting to solve the problem mathematically. Unfortunately, I ran into problems with this, as the mathematics proved too complicated to run on tick, and resulted in a jittering crosshair.
Instead, I came up with a novel solution. I created a custom trace channel called CameraCursor. I then attached an invisible collision box to the player at weapon height that would only block CameraCursor traces. Finally, I created Blueprint code that drew a CameraCursor line trace from the camera to the cursor on tick. Wherever this line trace contacted the collision box would be the location of the cursor.
This solution proved very effective and instantly solved Problems A and B. The crosshair now behaved properly, staying at gun height and always lining up perfectly with the cursor no matter where the player aimed.
To get a better understanding of the relative ‘cost’ of different techniques, I ran my solution past Zafar Qamar, a programming lecturer at Birmingham City University. Zafar was impressed with the solution, and confirmed that it was an inexpensive solution to an otherwise tricky problem.
Left: The character actor with the collision box highlighted Right: The crosshair lines up with the cursor now
Efficiently Coded Projectiles
Given that video games rely on thousands of lines of code being run every second, it makes sense to make that code as efficient as possible to ensure no processing power is wasted and the player experience isn’t negatively impacted. The following is an example of one way in which I made my Blueprint programming more efficient. My first attempt at coding projectile impacts was less than efficient. I used interfaces to send messages from the projectile to the object hit, but I used multiple Blueprint branch nodes to determine which message to send.
Inefficient projectile code. It runs three checks to call three different messages.
Using this method meant that every projectile hit would cause these branch checks to run irrespective of what was hit. Not a very efficient method, especially considering that most projectiles miss objects that react to being shot. The majority of these checks would therefore be pointless, eating up processing power but having no impact on the player experience.
For a game the size and scope of Impossible Rescue, this inefficient code probably wouldn’t have affected things too badly, but this level of inefficiency on a grander scale, affecting more interactions could potentially have an impact. I wanted to learn to do things properly, as a greater understanding of Blueprinting will aid cross-discipline communication.
I quickly came up with an alternative solution.
Upon hitting an object, a single branch node checks whether the object hit has the tag “Enemy”, “Breakable”, or "Shootable". If the object has any of these tags, a message is sent via interface to that object, passing along the amount of damage and the damage type (e.g. damaging or stun). They key difference here is that the message sent is always the same, so there’s no need to calculate exactly what was hit whenever a projectile impacts an object.
The efficient solution. It runs one check to run one message. (Note: This solution may look more complex because it includes damage and damage type within the message being sent. The original solution would have needed this information adding to achieve the same result.)
So, if the message is always the same, how does the object know how to react to being hit?
The beautiful thing about interface messages is that they can be overridden on actor Blueprints, so the same message can trigger different reactions depending on the actor receiving them. In the below examples, you can see that the same message received (Event Projectile Hit) triggers different code in each actor. Not only that, but because the damage type is contained within the message, the message can trigger several different effects.
This method is both much more efficient than my original solution and much more flexible.
The 'Projectile Hit' message received on the enemy first checks damage type. If it's been hit by a damaging shot, it takes damage. If it's been hit by a stunning shot, it gets stunned.
The 'Projectile Hit' message received by a Breakable panel checks whether it was a damaging shot, then (if it was) permanently disables all linked forcefields.
Instance Editable Patrol Routes
I created the patrol drones so that their patrol routes could be adjusted per instance without needing to touch Blueprints. To do this, I added four scene components to the Enemy Parent Blueprint, labelling these ‘Start Location’, ‘Patrol Node 1’, ‘Patrol Node 2’, and ‘Patrol Node 3’. I then created Blueprint code that would collect the vector location of each scene component when the enemy spawns, compiling them into an array. The enemy spawns at the Start Location, then the Blueprint code pulls the first Patrol Node from the array and sends the enemy there. When the enemy reaches this Patrol Node, it pulls the next node location from the array. This repeats until it reaches the final Patrol Node, at which point it returns to its spawn location and the route starts again. The location of the Patrol Nodes is instance editable, meaning that each enemy placed into a level can have a unique patrol route defined by the level designer. This might sound relatively basic, but it demonstrates how Blueprints and actors can be created to provide level designers with tools they can use to quickly and easily create variety without needing to touch Blueprints themselves.
Blueprint code that collects Start and Patrol locations into an array
Blueprint code telling the enemy to move to the 'next patrol point'
Continuation of code in Fig 57. This increases or resets the value of 'next patrol point'. It then re-runs the 'Move To Next Location' event.
Comentarios