Navigate

Sunday, October 22, 2023

Ready, Set, Godot! #1: First Person Controller

 It's been a while since I've dedicated time to game development. I've missed it. Now I'm back...I'm also back on this blog because Tumblr has created obstacles to non-users to find content.

I've been really excited about getting started on Godot. I pursued Unity over Unreal because none of my computers at the time could run Unreal: hahaha! I liked that Unity was more simple if not bare bones, allowing you to build up to near Unreal graphics if you desired. But Unreal, it seemed like you were stuck at max settings. I liked how Unity reminded me of the graphics from my childhood, while Unreal always chased more effects. I also liked  coding in C# and the amount of support the community provided each other.

But then Unity went public. I had a strong suspicion that they would cease prioritizing making a good game engine and instead prioritizing milking money from their product. I mean, what else could you expect from for-profit organization, right? I looked into less greed-inspired game engines and found Godot. Given that it's open source, I imagine it won't abandon its users for big dollars, and it looks like the folks putting this together have done amazing work. For the style of games I'd like to make I think Godot 4 is exactly what I need.

Still...Godot is deceptively different from Unity. So much looked the same, but I've spent hours relearning basic principles I had memorized for Unity. But it's good for me, and I'm excited to be making headway.

I've had the project Candles in development for years. I want to finalize this project. But also have several other FPS projects I've wanted to make. So why not do them all? My goal is to push forward on many projects near simultaneously and use the lessons learned on each project to make the others better.

But first, I need a First Person Controller. 

Since it had been nearly a year since I took the Godot 3 tutorials, I wasn't really sure where to start. I remember each Node being limited to one script. That's a little confusing cause I'm used to loading several scripts onto a single object. But in a way, each node could be like a component in Unity...maybe. So I followed a tutorial. And I think it was this one:

While I used this as a starting point I made some changes for my needs. I included sprinting as well as decreased movement speed when jumping. I'm proud of that actually. It felt really weird to jump and fly through the air. I thought it would make sense to lose momentum in the air, so I added some code to achieve that and I think it feels pretty good.

extends CharacterBody3D

var SPEED = 0.0 #how fast does the player move
var running = false
@export var run_speed = 0.0
@export var walk_speed = 0.0
@export var air_speed = 0.0
@export var air_speed_reduction = 0.0

@export var JUMP_VELOCITY = 0.0 #how high can the player jump
@export var spin = 0.0 #turn speed for the player's mouse
@export var mouse_visible = false
@export var look_up = 0 #clamp angle for looking up
@export var look_down = 0 #clamp angle for looking down

var count_up = 0.0
var countdown = 10

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
@onready var camera_player:= $Camera_Player

func _ready():
    Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
    SPEED = walk_speed

func _unhandled_input(event)-> void:
    if event is InputEventMouseMotion:
        rotate_y(-event.relative.x * spin)
        camera_player.rotate_x(-event.relative.y * spin)
        camera_player.rotation.x = clamp(camera_player.rotation.x, deg_to_rad(look_down),deg_to_rad(look_up) )
       
    if Input.is_action_just_pressed("shift"):
        if running == true:
            running = false
        else:
            running = true
       
    if Input.is_action_just_pressed("ui_cancel"):
        if mouse_visible == false:
            Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
            mouse_visible = true
        else:
            Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
            mouse_visible = false

func _physics_process(delta):
    # Add the gravity.
    if not is_on_floor():
        velocity.y -= gravity * delta
        if running == true:
            Reduce_Air_Speed()
    else:
        if running == true:
            SPEED = run_speed
        else:
            SPEED = walk_speed

    # Handle Jump.
    if Input.is_action_just_pressed("ui_accept") and is_on_floor():
        velocity.y = JUMP_VELOCITY

    # Get the input direction and handle the movement/deceleration.
    # As good practice, you should replace UI actions with custom gameplay actions.
    var input_dir = Input.get_vector("left", "right", "forward", "back")
    var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
    if direction:
        velocity.x = direction.x * SPEED
        velocity.z = direction.z * SPEED
    else:
        velocity.x = move_toward(velocity.x, 0, SPEED)
        velocity.z = move_toward(velocity.z, 0, SPEED)
    

    move_and_slide()
    
func Reduce_Air_Speed():
    SPEED = SPEED - air_speed_reduction * get_physics_process_delta_time()
    if SPEED < air_speed:
        SPEED = air_speed

There are probably some obvious ways to improve this (feel free to advise) but I'm coming back from a coding hiatus and making a switch to an engine that doesn't work the same.


Sunday, May 17, 2020

Candles Update #16: Radio

It’s been a while. Lots of “life things”, but also, ever since the prototype was completed, I’ve been a little stuck on how to proceed toward a real product. While there are things happening in the design and narrative aspects behind the scenes, I’m excited to share a script update!

WHAT IT DOES:

The radio will be an object the player can pick up. It will automatically play static when picked up, and stop when dropped.

As the player nears significant objects/areas in the game, the radio may play music, messages, demonic noises–you know, stuff radios tend to pick up.

WHY MAKE THIS:

We need a way to deliver the story and cue the player to explore and experiment. The radio can be an interactive way to do that. Hopefully we can hide clues about the story or objectives within the radio’s audio. Or, if we need, straight up tell the player what to do.

CODE:

image

Here, I set the radio to play audio when it is held and to stop when it is dropped.

The message audio and source transform are assigned by another script. This way, I can use triggers to set what the message audio is. I can also detect how close the player is to the audio source and adjust the volume of both the static and message. I grab the volume of the static and source message as set in editor so they never get louder than needed.

I made a list of game objects, but that was before I was sure how this script would function. So I should probably just delete it and the “AssignRadioScript” function as well.

image
This script holds the secret message audio, volume, and the source location. When the player enters the trigger–it assigns this to the radio’s public variables. If the player leaves the trigger area, then everything is unassigned.

So far it seems to all function pretty well. I look forward to creating some relevant messages/audio to put into the game!

Tuesday, November 26, 2019

Candles: Sin AI (Part 2): NavMesh'n Around

Previously, I shared how Sin sensed the player. Now I want to cover how Sin gets around. It’s fairly straight forward: I use a NavMesh and NavMeshAgent – which is all built into Unity (so my work is done). What’s not so simple is behavior. To imply behavior, I need Sin to patrol, respond to sounds, chase the player, and investigate last known locations.

To simplify things, I’ve given Sin only one target–aptly named “Sin Target”.
image
Whenever I want Sin to go somewhere, I simply move his target to that location.

So–if the Sin can see the player, Sin Target’s position becomes equal to the player’s position. Now Sin appears to chase the player, when really, he’s just chasing the same target toward which he’s always moving.

But it’s also handy when dealing with Sin’s Patrols:

In every room there is an empty object named after the room it’s placed in. I’ve put these all in a list, kept on Sin. They’re separated by floor.
image
When Sin reaches a destination–the Sin Patrol script randomly chooses a new destination from the list. But, it is based on what floor the player is on. This keeps Sin relatively close.
image

I’ve considered creating routes for Sin–but we’ll see if it’s needed.

Bonus topic: Sin Opens Doors

There are lots of doors that the player can open and close. We can’t have Sin just passing through like a ghost, even though…he is…But then, why not just have him walk through walls?

Anyway, I created a simple script to allow Sin to open doors when he tries to pass through them.
image
image
One of the little details I didn’t think of at first, I have to tell the doors that Sin is nearby–otherwise the player can shut the door on Sin’s face. And then Sin would appear to walk through it. So the line “openThisDoor.sinHere = true;” helps with that.

Next–I’ll share Sin’s Brain.

Monday, November 11, 2019

Candles Update: Sin A.I. Part 1: Sin Sense

It's been 10 months since a Candles Update...but I have still been working. There's actually a fair amount to catch up on. Also, I've been doing updates on Tumblr.

(...not just because I work there...)

https://zachmakesgames.tumblr.com

So my plan for future posts is to be more focused and not just cramming every update into a single post.

The core gameplay of Candles is a ghost/demon/monster hunting the player. I did not seek or follow any stealth-gameplay guides created by others, but I peeked at a few to solve problems I faced. For the most part–I wanted to design the AI to meet my needs, and not fall in line with what someone else thought was necessary.

I cannot overstate how aggravating this process for Sin’s AI has been. I can say that it went through many iterations, weeks of stalling, and in the end–I learned many new things.

Step 1 (I decided) was giving Sin (my bad guy) senses. Enter my script “SinSense”.


image
This script is mostly made of booleans. It doesn’t do very much, but several scripts either make changes or track the changes to this script.

Outside of booleans, I only track whether the player is moving, if they’re sprinting, and are they close to Sin. Thus defining whether Sin can hear the player.


image
This script represents Sin’s FOV. I stole the FOV bit and added a ray cast. Basically it detects whether the player should be within a cone of view from Sin. If the player is within said cone, then it fires a ray cast to see if Sin can actually see the player. It then makes changes to the SinSense bools accordingly.


image
SinHearPlayer is a script that does a little more than SinSense. If the player is heard (while not having been heard recently) it attracts Sin’s attention: that is, it stops Sin from moving. Other scripts actually rotate Sin toward the sound.


image
That covers the “Sensing” part of Sin. Still missing, taste, smell, and and touch, I guess. But Sin’s not really a dinosaur, so I’m not sure those others are as important.

Thursday, April 11, 2019

New Project: Candles

I'm starting a new project.

...three months ago...

I had been working on BrainMart for several months. Developing the features of that project has taught me a hundred new things about Unity, and I'm really excited about the doors that knowledge opens. But for now, I must say goodbye to BrainMart.

I cared a lot about the project. I think it was an interesting idea that represented a big part of my life in retail. But it didn't have much, if any, narrative potential. And while I like interesting ideas, making story interactive is my goal--and that's not possible when there's no story.

Enter: CANDLES (working title)

A game with story (and candles).

Candles, A short history:

Candles was conceived around the same time as BrainMart. And so, shared some basic design philosophy. I wanted to create a horror game based around a simple, original mechanic. The player would explore a haunted house (very original) while being hunted by a demon/ghost/killer (astoundingly original).

BUT, it's supposed to be about the candles. The player can find and carry candles. They can obviously be used as a light source, but their true power is impeding the hunter-demon's path. By throwing a lit candle on the ground, the player creates an obstacle of light the hunter-demon cannot pass. And while that helps create a safe space, the candles would eventually burn out--making resource management an aspect of the game as well.

I was quite set on the design and mechanics, I just needed a story. And while I came up with a narrative to slap on top of the project, I found it bland and unsurprising. I wanted the story to be weird, and perplexing. And running circles in my head wasn't getting me to where I wanted to go.

Help was needed.

That's when I invited an old friend, Kirk Gunton, to join me on the project. He's a filmmaker that works primarily in North Carolina. We graduated from the film program at WCU together. We shared screenwriting, and other classes. And I was the Assistant Director on his Capstone Project, Red Springs.

Needless to say, we have a history of collaboration, and I think it allowed us to "hit the ground running." Within our first few conversations, I became exponentially more excited about the project and its potential. So much so, I decided to put BrainMart to rest and start prototyping Candles.

Prototype Development:

1. FPS Controls:

I copied over my controls from the project FPS (my shooter-shopping game). Unity has a first-person controller, fully decked out--but I liked the simplicity of the hand-typed script, as I feel it lets me add in features only when I need them.

Speaking of--I added a run toggle. By pressing shift, the player changes their speed. I plan to create a limit--so the player may only spring for short distances before needing to "catch their breath."

2. Level Design:

I created a testing layout. It's a two-story house with heavy influence from Victorian houses. Mostly because they have a great aesthetic and are very "twisty-turny" inside, allowing for a maze layout and lots of nooks and crannies.


Most rooms and areas have more than one entrance/exit, which should allow the player a route of escape. And because I like to look at more than grey walls while I develop, I dropped in some basic textures to give a little life to the prototype environments.


3. Doors:

My first challenge. Creating an opening and closing door. I've always struggled with rotation in Unity, so was scared to attempt this. And while Animator is probably the "smart" way to handle this--I wanted to challenge myself and grow as a scripter by doing it all in C#. To my surprise, I had quick success.


I used a coroutine to open and close the door--but I also wanted to interrupt the action. That took a little more work, and I was finally able to get everything working. I'm planning to revisit the script, allowing the door to "pause" if it touches the player.

4. Ray Cast and Dots:

I used a ray cast to determine the player was near an object and looking at it. I think it stretches a meter and a half. Immediately, I found it very difficult to know whether I was in-range and on target. So I created a simple UI script that shows a white dot, and when the player is in range of an object, the dot grows in size. Subtle and helpful.


5. Pick Ups:

Now that my ray cast was functioning, time to pick things up. Originally, I had planned on making a 6-slot candle inventory. While in the process of figuring out how to get an object to child itself to the player and also go to a predetermined "hold" position that made the object visible on screen--I felt it might be interesting to limit the player to any two objects. One in the left hand and one in the right.


This may completely change the path of development, but I like the simplicity of the inventory system. You don't have to open a window or worry about onscreen UI, just--you have what's in your hands. I used Q to control the left hand and E to control the right. Not only do you pick up objects with these keys, but you drop them with these keys also. Simple.







6. Shrines and Keys:

Back when the project was very simple, the idea was that you would look for "keys": objects of great importance, like an old picture or family heirloom; and have to take them to their corresponding "Shrines"--like a fireplace mantel or dresser. This may change--but I've created some basic shrines and keys none the less. They're functional--as much as they can be without having specific identity and purpose.

To be continued...

My next step is to create the monster/ghost/hunter. Creating a NavMesh agent that tracks the player is nothing. But I want to create stealth mechanics and hunter AI. I really have no idea what I'm doing, so it's going to be fun to put all this together. Once that's in, I feel we'll have a game and can start exploring story implementation!




Thursday, November 15, 2018

Brain*Mart: Instantiating UI and Berserking Zombies

So last time I discussed a few goals I would like to reach for the project:
1. Clean up the code.
2. Add some art.
3. Create a demo.
4. click-able (mouse) controls.
5. Audio.
6. Transition zombie thought clouds to the UI instead of game-space.

I was not insanely productive.

Possibly because I expected it to be the most challenging, I focused on number 6, Turn the world-space thought cloud UI into actual UI elements on Canvas. This would solve a few issues--such as the thought clouds rotating with the zombie as it changed directions, as well as, looking kind of odd floating feet above the rest of the world.

Before I get into the details of the latest updates, here's a quick video showing off a few of the accomplishments since the last update:



So I needed a UI element that translated world-space coordinates to 2D coordinates on the canvas. Within five minutes of searching, I found a great video on Youtube that showed a simple way to achieve this:

https://www.youtube.com/watch?v=0bvDmqqMXcA.

Following along, I was able to get a text object to follow the player, and later a zombie. I was super excited and imagined I had achieved an early success.

BUT, because my zombies are instantiated, that means I have to instantiate the UI elements too. Not only do they need to be instantiated, but I have to be able to turn them on and off, as well as adjust colors. And since they were on the UI canvas, and not children of the zombie--I had to find them, reference them, and get access to their scripts. Because I wasn't using childed objects, I had to rewrite almost every script to gain access to the UI elements in the canvas. I feel like my GetComponent references resemble that of an old telephone switchboard being operated by a chimpanzee.

It took me the whole week of working a few hours a night. And it had the wonderful result of setting me back on my first objective (cleaning up the code). It's been a real joy figuring this all out.

Once I succeeded, it was a wonderful feeling. Though I did create some annoying bugs in several other scripts.

But the more exciting news for me was figuring out the Berserk UI feature. This has been my largest worry. I knew since I originally conceived this idea, years ago, I wanted zombies to get so hungry that they went Berserk and tried to eat the people in the store. And I knew I wanted to illustrate that timer with a thought cloud that slowly filled up with a color (red in my case). But I didn't have a clue how to incorporate it. Luckily, I found another helpful video:

https://www.youtube.com/watch?v=4fYd6-RFp_M.

The script looked really simple, so I skipped to copying and trying to make it work myself. FAIL. Only by watching the video did I realize that Unity had handy sprite fill options in the image component. It's almost too easy to use, but I quickly got my Berserk Visual Timer up and running, making the project feel like it's really coming along.

After that, I needed a break--finally beating Halo 3 and getting pretty far into Fable. Xbox Game Pass is a very dangerous distractor--but playing great games is also really inspiring.

I've since created a functioning Berserk Mode that causes the zombie to flash red and chase the player at an increased speed. If the player doesn't feed the zombie before the berserk mode is over, the zombie simply leaves.

Tuesday, October 23, 2018

Brain*Mart: On the Road to Playable Demo

Didn't make huge progress this week. I may, or may not, have been a little distracted by playing a Vanilla World of Warcraft server. I find the game really inspiring--while it also steals my free time, thus preventing me from acting on the inspiration. Point is--I didn't make the leaps and bounds I had accomplished on the first week.

I did do a few things:

1. Level Building: I put together an environment. It's about three screen-sizes squared--not so big that you get lost, but does require some work moving around and getting to the zombies. The level also includes a sidewalk. It's inaccessible to the player through MathF.Clamp. Anyway, the zombies come in off the street and then leave the same way. Speaking of...



2. Sliding Door: To create an entrance for the zombies, I decided to make a sliding door with four panels. Just for funsies. But it turned out more challenging than I expected. I think it's because the whole time I was writing the code, I kept thinking up future problems. So I spent a lot of time trying to solve problems that didn't exist yet. 20 lines of code later and I was so lost I didn't know what to do. So I started from scratch and it works.


3. Zombie Spawner -- This was essential. I needed a way to "design" the game without having to code every level. So the zombie spawner does more than just instantiate zombies. I have a max amount of zombies that can spawn ever, which can be overridden by clicking the "Unlimited" bool to true. I also have a limit on how many zombies can be in the scene at one time, as well as, how much time must pass before the next zombie can spawn.


I'm still contemplating on ways to mix it up. Should I create waves? Should I designate which brain each zombie should have? Should I set different berserk time lengths? Should it be random and influenced by chosen difficulty?

Definitely worth testing.

4. Nav Route Selector -- Gave me a little trouble. I have game objects I call "Routes". Each route has a list of "Nav Points". I fill the nav points with trigger boxes that serve to both be a target vector as well as set the zombie on a new target when it's reached.

Here's a video of a quick playthrough:


My next goals:

1. Clean up my code. I've been listening to Unity scripting tutorials at work and they advise keeping scripts short and focused. I agree--as my scripts have become a bit unruly. That's mostly from discovering what I need the script to do as I write it. Since I see how everything is working as a whole, I can hopefully streamline a lot of it.

2. Stand-in art. I want to get some 2D art for the player and zombies in the game. I feel as I start testing, that will make the experience more enjoyable for players. I think the environment can be more abstract, but seeing pills from Dr. Mario walk around is probably off-putting.

3. Create a demo. I want to get people playing this game asap. It's essentially playable--but I would like to have: a. A Menu. B. Tutorial--playable--to explain the mechanics. C. End screen with player score.

4. Click Controls. I ultimately see this being on mobile, so I need to start creating a control scheme that translates easily into touch. Hopefully I could do that before finishing a demo.

5. Stretch goal--some audio.

6. OH, and I almost forgot. I feel the floating brain cards and zombie clouds are little--weird. having them float about in 3D space isn't quite working for me. I'm considering making them UI elements on the 2D canvas. I found a video on how to accomplish that, so I want to play around with how that affects the experience.