• Advertisement

Martin H Hollstein

Member
  • Content count

    59
  • Joined

  • Last visited

Community Reputation

647 Good

About Martin H Hollstein

  • Rank
    Member

Personal Information

Social

  • Twitter
    sm_artin
  • Github
    hollsteinm
  1. Eight Hours by Troll Purse Supernatural Investigation Horror Game Jump right into it from either itch.io or gamejolt! Take on the role of Bob Norm, paranormal investigator. Begin your night locked in a haunted house. It is your job to capture apparitions residing within the abode. But, not all is as it seems. Something sinister lurks within the house at night. Can you survive Eight Hours? Eight Hours is the horrifying debut game from Troll Purse. It features you as Bob Norm, a paranormal investigator. At your request, the owner has locked you in the house for the duration of the investigation. Now, it is up to you to discover the secrets of the haunting. You have eight hours - good luck and Godspeed. See more at the official game webpage. Continue to explore more and get the download links to play today! Use Supernatural Detection Equipment to Hunt Haunts Explore A Seemingly Normal Haunted House You can get the game from either itch.io or gamejolt!
  2. Designing Player World Interaction in Unreal Engine 4

    Thanks, I will fix that up in time. Any thoughts on the content other than that? Was this useful?
  3. Originally posted on Troll Purse development blog. Unreal Engine 4 is an awesome game engine and the Editor is just as good. There are a lot of built in tools for a game (especially shooters) and some excellent tutorials out there for it. So, here is one more. Today the topic to discuss is different methods to program player world interaction in Unreal Engine 4 in C++. While the context is specific to UE4, it can also easily translate to any game with a similar architecture. Interaction via Overlaps By and far, the most common tutorials for player-world interaction is to use Trigger Volumes or Trigger Actors. This makes sense, it is a decoupled way to set up interaction and leverages most of the work using classes already provided by the engine. Here is a simple example where the overlap code is used to interact with the player: Header // Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "InteractiveActor.generated.h" UCLASS() class GAME_API InteractiveActor : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties InteractiveActor(); virtual void BeginPlay() override; protected: UFUNCTION() virtual void OnInteractionTriggerBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); UFUNCTION() virtual void OnInteractionTriggerEndOverlap(UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex); UFUNCTION() virtual void OnPlayerInputActionReceived(); UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Interaction) class UBoxComponent* InteractionTrigger; } This is a small header file for a simple base Actor class that can handle overlap events and a single input action. From here, one can start building up the various entities within a game that will respond to player input. For this to work, the player pawn or character will have to overlap with the InteractionTrigger component. This will then put the InteractiveActor into the input stack for that specific player. The player will then trigger the input action (via a keyboard key press for example), and then the code in OnPlayerInputActionReceived will execute. Here is a layout of the executing code. Source // Fill out your copyright notice in the Description page of Project Settings. #include "InteractiveActor.h" #include "Components/BoxComponent.h" // Sets default values AInteractiveActor::AInteractiveActor() { PrimaryActorTick.bCanEverTick = true; RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Root")); RootComponent->SetMobility(EComponentMobility::Static); InteractionTrigger = CreateDefaultSubobject<UBoxComponent>(TEXT("Interaction Trigger")); InteractionTrigger->InitBoxExtent(FVector(128, 128, 128)); InteractionTrigger->SetMobility(EComponentMobility::Static); InteractionTrigger->OnComponentBeginOverlap.AddUniqueDynamic(this, &ABTPEquipment::OnInteractionProxyBeginOverlap); InteractionTrigger->OnComponentEndOverlap.AddUniqueDynamic(this, &ABTPEquipment::OnInteractionProxyEndOverlap); InteractionTrigger->SetupAttachment(RootComponent); } void AInteractiveActor::BeginPlay() { if(InputComponent == nullptr) { InputComponent = ConstructObject<UInputComponent>(UInputComponent::StaticClass(), this, "Input Component"); InputComponent->bBlockInput = bBlockInput; } InputComponent->BindAction("Interact", EInputEvent::IE_Pressed, this, &AInteractiveActor::OnPlayerInputActionReceived); } void AInteractiveActor::OnPlayerInputActionReceived() { //this is where logic for the actor when it receives input will be execute. You could add something as simple as a log message to test it out. } void AInteractiveActor::OnInteractionProxyBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) { if (OtherActor) { AController* Controller = OtherActor->GetController(); if(Controller) { APlayerController* PC = Cast<APlayerController>(Controller); if(PC) { EnableInput(PC); } } } } void AInteractiveActor::OnInteractionProxyEndOverlap(UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) { if (OtherActor) { AController* Controller = OtherActor->GetController(); if(Controller) { APlayerController* PC = Cast<APlayerController>(Controller); if(PC) { DisableInput(PC); } } } } Pros and Cons The positives of the collision volume approach is the ease at which the code is implemented and the strong decoupling from the rest of the game logic. The negatives to this approach is that interaction becomes broad when considering the game space as well as the introduction to a new interactive volume for each interactive within the scene. Interaction via Raytrace Another popular method is to use the look at viewpoint of the player to ray trace for any interactive world items for the player to interact with. This method usually relies on inheritance for handling player interaction within the interactive object class. This method eliminates the need for another collision volume for item usage and allows for more precise interaction targeting. Source AInteractiveActor.h // Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "InteractiveActor.generated.h" UCLASS() class GAME_API AInteractiveActor : public AActor { GENERATED_BODY() public: virtual OnReceiveInteraction(class APlayerController* PC); } AMyPlayerController.h // Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "GameFramework/PlayerController.h" #include "AMyPlayerController.generated.h" UCLASS() class GAME_API AMyPlayerController : public APlayerController { GENERATED_BODY() AMyPlayerController(); public: virtual void SetupInputComponent() override; float MaxRayTraceDistance; private: AInteractiveActor* GetInteractiveByCast(); void OnCastInput(); } These header files define the functions minimally needed to setup raycast interaction. Also note that there are two files here as two classes would need modification to support input. This is more work that the first method shown that uses trigger volumes. However, all input binding is now constrained to the single ACharacter class or - if you designed it differently - the APlayerController class. Here, the latter was used. The logic flow is straight forward. A player can point the center of the screen towards an object (Ideally a HUD crosshair aids in the coordination) and press the desired input button bound to Interact. From here, the function OnCastInput() is executed. It will invoke GetInteractiveByCast() returning either the first camera ray cast collision or nullptr if there are no collisions. Finally, the AInteractiveActor::OnReceiveInteraction(APlayerController*) function is invoked. That final function is where inherited classes will implement interaction specific code. The simple execution of the code is as follows in the class definitions. AInteractiveActor.cpp void AInteractiveActor::OnReceiveInteraction(APlayerController* PC) { //nothing in the base class (unless there is logic ALL interactive actors will execute, such as cosmetics (i.e. sounds, particle effects, etc.)) } AMyPlayerController.cpp AMyPlayerController::AMyPlayerController() { MaxRayTraceDistance = 1000.0f; } AMyPlayerController::SetupInputComponent() { Super::SetupInputComponent(); InputComponent->BindAction("Interact", EInputEvent::IE_Pressed, this, &AInteractiveActor::OnCastInput); } void AMyPlayerController::OnCastInput() { AInteractiveActor* Interactive = GetInteractiveByCast(); if(Interactive != nullptr) { Interactive->OnReceiveInteraction(this); } else { return; } } AInteractiveActor* AMyPlayerController::GetInteractiveByCast() { FVector CameraLocation; FRotator CameraRotation; GetPlayerViewPoint(CameraLocation, CameraRotation); FVector TraceEnd = CameraLocation + (CameraRotation.Vector() * MaxRayTraceDistance); FCollisionQueryParams TraceParams(TEXT("RayTrace"), true, GetPawn()); TraceParams.bTraceAsyncScene = true; FHitResult Hit(ForceInit); GetWorld()->LineTraceSingleByChannel(Hit, CameraLocation, TraceEnd, ECC_Visibility, TraceParams); AActor* HitActor = Hit.GetActor(); if(HitActor != nullptr) { return Cast<AInteractiveActor>(HitActor); } else { return nullptr; } } Pros and Cons One pro for this method is the control of input stays in the player controller and implementation of input actions is still owned by the Actor that receives the input. Some cons are that the interaction can be fired as many times as a player clicks and does not repeatedly detect interactive state without a refactor using a Tick function override. Conclusion There are many methods to player-world interaction within a game world. In regards to creating Actors within Unreal Engine 4 that allow for player interaction, two of these potential methods are collision volume overlaps and ray tracing from the player controller. There are several other methods discussed out there that could also be used. Hopefully, the two implementations presented help you decide on how to go about player-world interaction within your game. Cheers! Originally posted on Troll Purse development blog.
  4. Designing Player World Interaction in Unreal Engine 4

    Originally posted on Troll Purse development blog. Unreal Engine 4 is an awesome game engine and the Editor is just as good. There are a lot of built in tools for a game (especially shooters) and some excellent tutorials out there for it. So, here is one more. Today the topic to discuss is different methods to program player world interaction in Unreal Engine 4 in C++. While the context is specific to UE4, it can also easily translate to any game with a similar architecture. Interaction via Overlaps By and far, the most common tutorials for player-world interaction is to use Trigger Volumes or Trigger Actors. This makes sense, it is a decoupled way to set up interaction and leverages most of the work using classes already provided by the engine. Here is a simple example where the overlap code is used to interact with the player: Header // Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "InteractiveActor.generated.h" UCLASS() class GAME_API InteractiveActor : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties InteractiveActor(); virtual void BeginPlay() override; protected: UFUNCTION() virtual void OnInteractionTriggerBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); UFUNCTION() virtual void OnInteractionTriggerEndOverlap(UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex); UFUNCTION() virtual void OnPlayerInputActionReceived(); UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Interaction) class UBoxComponent* InteractionTrigger; } This is a small header file for a simple base Actor class that can handle overlap events and a single input action. From here, one can start building up the various entities within a game that will respond to player input. For this to work, the player pawn or character will have to overlap with the InteractionTrigger component. This will then put the InteractiveActor into the input stack for that specific player. The player will then trigger the input action (via a keyboard key press for example), and then the code in OnPlayerInputActionReceived will execute. Here is a layout of the executing code. Source // Fill out your copyright notice in the Description page of Project Settings. #include "InteractiveActor.h" #include "Components/BoxComponent.h" // Sets default values AInteractiveActor::AInteractiveActor() { PrimaryActorTick.bCanEverTick = true; RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Root")); RootComponent->SetMobility(EComponentMobility::Static); InteractionTrigger = CreateDefaultSubobject<UBoxComponent>(TEXT("Interaction Trigger")); InteractionTrigger->InitBoxExtent(FVector(128, 128, 128)); InteractionTrigger->SetMobility(EComponentMobility::Static); InteractionTrigger->OnComponentBeginOverlap.AddUniqueDynamic(this, &ABTPEquipment::OnInteractionProxyBeginOverlap); InteractionTrigger->OnComponentEndOverlap.AddUniqueDynamic(this, &ABTPEquipment::OnInteractionProxyEndOverlap); InteractionTrigger->SetupAttachment(RootComponent); } void AInteractiveActor::BeginPlay() { if(InputComponent == nullptr) { InputComponent = ConstructObject<UInputComponent>(UInputComponent::StaticClass(), this, "Input Component"); InputComponent->bBlockInput = bBlockInput; } InputComponent->BindAction("Interact", EInputEvent::IE_Pressed, this, &AInteractiveActor::OnPlayerInputActionReceived); } void AInteractiveActor::OnPlayerInputActionReceived() { //this is where logic for the actor when it receives input will be execute. You could add something as simple as a log message to test it out. } void AInteractiveActor::OnInteractionProxyBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) { if (OtherActor) { AController* Controller = OtherActor->GetController(); if(Controller) { APlayerController* PC = Cast<APlayerController>(Controller); if(PC) { EnableInput(PC); } } } } void AInteractiveActor::OnInteractionProxyEndOverlap(UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) { if (OtherActor) { AController* Controller = OtherActor->GetController(); if(Controller) { APlayerController* PC = Cast<APlayerController>(Controller); if(PC) { DisableInput(PC); } } } } Pros and Cons The positives of the collision volume approach is the ease at which the code is implemented and the strong decoupling from the rest of the game logic. The negatives to this approach is that interaction becomes broad when considering the game space as well as the introduction to a new interactive volume for each interactive within the scene. Interaction via Raytrace Another popular method is to use the look at viewpoint of the player to ray trace for any interactive world items for the player to interact with. This method usually relies on inheritance for handling player interaction within the interactive object class. This method eliminates the need for another collision volume for item usage and allows for more precise interaction targeting. Source AInteractiveActor.h // Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "InteractiveActor.generated.h" UCLASS() class GAME_API AInteractiveActor : public AActor { GENERATED_BODY() public: virtual OnReceiveInteraction(class APlayerController* PC); } AMyPlayerController.h // Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "GameFramework/PlayerController.h" #include "AMyPlayerController.generated.h" UCLASS() class GAME_API AMyPlayerController : public APlayerController { GENERATED_BODY() AMyPlayerController(); public: virtual void SetupInputComponent() override; float MaxRayTraceDistance; private: AInteractiveActor* GetInteractiveByCast(); void OnCastInput(); } These header files define the functions minimally needed to setup raycast interaction. Also note that there are two files here as two classes would need modification to support input. This is more work that the first method shown that uses trigger volumes. However, all input binding is now constrained to the single ACharacter class or - if you designed it differently - the APlayerController class. Here, the latter was used. The logic flow is straight forward. A player can point the center of the screen towards an object (Ideally a HUD crosshair aids in the coordination) and press the desired input button bound to Interact. From here, the function OnCastInput() is executed. It will invoke GetInteractiveByCast() returning either the first camera ray cast collision or nullptr if there are no collisions. Finally, the AInteractiveActor::OnReceiveInteraction(APlayerController*) function is invoked. That final function is where inherited classes will implement interaction specific code. The simple execution of the code is as follows in the class definitions. AInteractiveActor.cpp void AInteractiveActor::OnReceiveInteraction(APlayerController* PC) { //nothing in the base class (unless there is logic ALL interactive actors will execute, such as cosmetics (i.e. sounds, particle effects, etc.)) } AMyPlayerController.cpp AMyPlayerController::AMyPlayerController() { MaxRayTraceDistance = 1000.0f; } AMyPlayerController::SetupInputComponent() { Super::SetupInputComponent(); InputComponent->BindAction("Interact", EInputEvent::IE_Pressed, this, &AInteractiveActor::OnCastInput); } void AMyPlayerController::OnCastInput() { AInteractiveActor* Interactive = GetInteractiveByCast(); if(Interactive != nullptr) { Interactive->OnReceiveInteraction(this); } else { return; } } AInteractiveActor* AMyPlayerController::GetInteractiveByCast() { FVector CameraLocation; FRotator CameraRotation; GetPlayerViewPoint(CameraLocation, CameraRotation); FVector TraceEnd = CameraLocation + (CameraRotation.Vector() * MaxRayTraceDistance); FCollisionQueryParams TraceParams(TEXT("RayTrace"), true, GetPawn()); TraceParams.bTraceAsyncScene = true; FHitResult Hit(ForceInit); GetWorld()->LineTraceSingleByChannel(Hit, CameraLocation, TraceEnd, ECC_Visibility, TraceParams); AActor* HitActor = Hit.GetActor(); if(HitActor != nullptr) { return Cast<AInteractiveActor>(HitActor); } else { return nullptr; } } Pros and Cons One pro for this method is the control of input stays in the player controller and implementation of input actions is still owned by the Actor that receives the input. Some cons are that the interaction can be fired as many times as a player clicks and does not repeatedly detect interactive state without a refactor using a Tick function override. Conclusion There are many methods to player-world interaction within a game world. In regards to creating Actors within Unreal Engine 4 that allow for player interaction, two of these potential methods are collision volume overlaps and ray tracing from the player controller. There are several other methods discussed out there that could also be used. Hopefully, the two implementations presented help you decide on how to go about player-world interaction within your game. Cheers! Originally posted on Troll Purse development blog.
  5. Eight Hours

    Take on the role of Bob Norm, paranormal investigator. Begin your night locked in a haunted house. It is your job to capture apparitions residing within the abode. But, not all is as it seems. Something sinister lurks within the house at night. Can you survive Eight Hours? Eight Hours is the horrifying debut game from Troll Purse. It features you as Bob Norm, a paranormal investigator. At your request, the owner has locked you in the house for the duration of the investigation. Now, it is up to you to discover the secrets of the haunting. You have eight hours - good luck and Godspeed.
  6. General That Is Right, Discord Update Automation in AWS

    Originally posted on Troll Purse Dev Blog Recently, Troll Purse setup a public invite for the Troll Purse Discord Server. And, as with all things, we decided to test out using Discord Webhooks to push updates to our members in realtime. This is by far the most effective realtime pushing we have conceived yet. It was so easy, sharing it will be just as easy. Using A Simple Webhook Usually, the pattern at Troll Purse to push to third party accounts follows this pattern: Sign up for the third party account Register an application Find an API wrapper library for said third party account Publish an AWS Lambda Post about it! This time, we decided to skip step 3. For the most part, the developers at Troll Purse recognized that this push would require very little data transformation and authentication routines. In fact, all of the work was done in one POST request to the Troll Purse Discord Server. The Code, Kind Human public async Task<string> FunctionHandler(SNSEvent input, ILambdaContext context) { try { var messageJSONString = input.Records[0]?.Sns.Message; context?.Logger.LogLine($"Received({input.Records[0]?.Sns.MessageId}): {messageJSONString}"); if (messageJSONString != null) { var messageContent = JsonConvert.DeserializeObject<BlogContentUpdated>(messageJSONString); using (var httpClient = new HttpClient()) { string payload = $"{"content":"{messageContent.PostTitle}. {messageContent.ContentSnippet}... {messageContent.PostLink}"}"; var response = await httpClient.PostAsync(Environment.GetEnvironmentVariable("discord_webhook"), new StringContent(payloadEncoding.UTF8, "application/json")); return response.StatusCode.ToString(); } } else { return null; } } catch (Exception e) { context?.Logger.LogLine("Unable to Discord the SNS message"); context?.Logger.LogLine(e.Message); context?.Logger.LogLine(e.StackTrace); return null; } } Notes: BlogContentUpdated is code defined in an external Troll Purse binary. WE USE SECURE ENVIRONMENT VARIABLES!!! THIS IS IMPORTANT!!!! (As opposed to plaintext credentials in our source code.) The Joy of Lambda All of these features that Troll Purse has blogged about are done within a few hours. This is easily aided by the idea of serverless programming. There is no overhead of provisioning servers, testing different server environments, and configuring a network for these functions. It removes a lot of network infrastructure and enables Troll Purse developers to create fast, reactive, internal services. Please, if you spend too much time configuring and setting up, try using AWS Lambda to speed up development time. Would You Look At That In two lines, without a library or API wrapper, our developers can now push blog updates to our Discord server. This is a nice quick feature that we plan on integrating in our automated build environment to push updates about new versions released to the public. Enjoy! Originally posted on Troll Purse Dev Blog
  7. Learning How Troll Purse Easily Setup Forums in AWS

    How Troll Purse Easily Setup Forums in AWS Originally Posted on Troll Purse Dev Blog After our migration to AWS, Troll Purse removed the old forums running in Digital Ocean. Troll Purse decided to start with a clean slate. Which was easy - as nobody registered (no migrations needed, just nuclear destruction of the service)! A curse that turned into a blessing. Troll Purse can now scale the forums based on usage and save some money on infrastructure. This will allow us to put more effort into our games! How To Troll Purse decided to share with you how to set up this type of environment. S3 Configuration For hosting content uploaded by Troll Purse forum users, S3 was used to store images. Since NodeBB has a nice S3 upload plugin, there was little to no work other than configuration needed to enable the feature. S3 on the otherhand, required configuration to allow access from http://forums.trollpurse.com. However, it also needed to allow access to the real DNS hostname (according to AWS) for the actual server to update data. This meant a custom S3 CORS policy and S3 Bucket Policy. Finally, the role our server would assume needed to have full access to S3 buckets. Further, Troll Purse could restrict access by bucket name. Below are examples Troll Purse Built up to help restrict access to an S3 bucket. Note, AWS will still mark it as public. However, there was a configuration that allowed public GET without S3 being marked public. S3 Bucket Policy { "Version": "2012-10-17", "Id": "website access bucket Policy", "Statement": [ { "Sid": "Allow get requests originating from your domain.", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::your-bucket-name/*", "Condition": { "StringLike": { "aws:Referer": "http://your-domain-name/*" } } }, { "Sid": "Deny get requests not originating from your domain.", "Effect": "Deny", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::your-bucket-name/*", "Condition": { "StringNotLike": { "aws:Referer": "http://your-domain-name/*" } } }, { "Sid": "Create, Update, Delete for ARN", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::xxxxxxxxx:role/your-role-used-for-s3-access-and-management" }, "Action": [ "s3:PutObject", "s3:GetObject", "s3:DeleteObject" ], "Resource": "arn:aws:s3:::your-bucket-name/*" } ] } S3 CORS Policy <?xml version="1.0" encoding="UTF-8"?> <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <CORSRule> <AllowedOrigin>your-domain-name</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <AllowedMethod>PUT</AllowedMethod> <AllowedMethod>POST</AllowedMethod> <AllowedMethod>DELETE</AllowedMethod> <MaxAgeSeconds>3000</MaxAgeSeconds> <AllowedHeader>Authorization</AllowedHeader> </CORSRule> </CORSConfiguration> Redis Configuration For Redis, Troll Purse used default configurations provided by AWS ElastiCache. This cache was put in a private subnet, accessible only to services in the Troll Purse VPC as configured. Currently, Troll Purse is using the free tier cache.t2.micro instance. Other than that, the Launch Configuration just needs a reference to the public DNS of the cache. VPC Configuration AWS VPC is great for creating logically segregated services for an environment. Subnets Following normal AWS architecture diagrams (shown below), Troll Purse created two subnets. There is the public subnet which will host the forum instances and the load balancer. There is then the private subnet which has no internet access. The private subnet contains the forum’s Redis service. Security Groups Troll Purse setup two different Security Groups. One for services bound to the public subnet and another for services bound to the private subnet. The only real different is how the inboud internet traffic is configured. The public security group allows inbound internet traffic. The private security group does not allow inbound internet traffic. This is further strengthened by Route Tables Route Tables The Route Tables used were configured according to the afore mentioned diagram. There were two Route Tables. The first route table was created for the public subnet. This allows internet traffic in via the Internet Gateway bound to the public subnet. The second route table created was the private subnet. This Route Table did not receive configuration for public internet access. IAM Role Configuration To get our environment up using NodeBB with Redis, Troll Purse created a new IAM Role for EC2 instances meant to host NodeBB. This role did not need a lot of thought put into it. All it needed was full S3 Access, and full Redis access. From here Troll Purse uses two more AWS services to provide data storage for the forums. Auto-Scaling Configuration Using our existing configuration, Troll Purse created an Auto Scaling Configuration using the base Amazon Linux AMI on a t2.micro instance. We don’t do anything else special. Troll Purse set the default configurations of Min instances to 1 and Max instances to 1. This ensures the service will always be running one instance, whether it fails or not. Note: Make sure to use ELB Health Checks - this will verify the web service is actually running on the instance Launch Configuration User Data Here is a wonderful gist provided by one of our AWESOME developers (Disclaimer: I authored this post - totally biased opinion) used as a Launch Configuration. Soon Troll Purse will take away half of that setup and make an image for EC2 to use. Then only NodeBB configuration and launch information is required for the Launch Configuration. EC2 Configuration There wasn’t anything to do for EC2 since all of our instance information was setup using Auto Scaling. Conclusion Setting up an environment in AWS for our forums took about two days of building and verifying. These changes required no code whatsoever. All Troll Purse had to do was select from a large suite of services to support desired results. So, now that they exsist, join up on the forums! Originally Posted on Troll Purse Dev Blog
  8. General Why Troll Purse Migrated to AWS

    No problem jbadams! Always glad to help. I had looked into Google cloud services, but the lack of community and, what seemed to me features for the cost, turned me off. It may have also hurt that configuration for service integration in Google cloud seemed wonky at best ( I had started to set up a Google Home integration service). The reason being is that there are configuration options in the console not only for Google cloud, but also for all of their other services (Google+ for instance). This made it feel like a management nightmare. I have experience in Azure, but I found it too rigid. If I was planning on going full Windows Stack, I may have actually gone with Azure. But, from past experience, the simple act of running the equivalent of an AMI was expensive. I actually had to shut down a repo server due to the overwhelming cost. I had also deployed a game out to Azure as well . It still runs but I found managing infrastructure a nightmare in Azure. AWS was a natural fit (disclaimer: I have since become a certified Solutions Architect) for what I wanted to achieve. Fast iterations of cloud infrastructure, simple UI on the web and a sensible CLI. From previous experience, I had deployed a defunct game and editor to AWS back in 2013/2014. That was the smoothest and most straight forward experience I had with cloud infrastructure. Now, I do find AWS great, but one thing that peeves me off right now is the lack of Dev Ops for Windows builds. I really want to use CodeBuild, CodeCommit, CodeDeploy (might as well as use CodePipeline at that) to start deploying and distributing ALL of my game builds in UE4. However, at this point, I will have to provision an AMI to build Windows, which means deploying a CI tool as well (such as Jenkins). I hope you find that information useful. If you have any other questions, feel free to ask! I really enjoy conversation and feedback.
  9. General Why Troll Purse Migrated to AWS

    Originally posted on Troll Purse Dev Blog Recently, Troll Purse made the decision to migrate from the cloud in Digital Ocean to Amazon Web Services for cloud and website services. There were several reasons behind this critical decision. These reasons are infrastructure, flexibility, and future plans. Infrastructure Digital Ocean has a nice setup. They have a slick looking User Interface, easy to find services, and awesome community driven documentation. Troll Purse quite liked Digital Ocean if not for a few issues regarding infrastructure in comparison to Amazon Web Services (AWS). Cost Digital Ocean was not overly expensive. However, their base monthly rate ($5 / month + $1 / month for backups), was still higher than hosting a static website on AWS. This is because, to host a static website and blog, Digital Ocean achieves this via Droplets. This does not scale the wallet very well - especially if traffic started to boom. Digital Ocean only offers referral bonuses. So, if we were famous - we could potentially host everything for free. Big gamble at this moment. AWS charges $0.50 / month forRoute 53 (DNS Name Servers) and a variable cost for S3 storage of our static website content. Based on traffic trends, the variable costs of AWS were a huge benefit. Finally, it helps that AWS offers a full year of various resources for free. Continuous Delivery Digital Ocean had a lot of APIs for Continous Delivery of our website, but it didn't offer a full suite of solutions for proper Developer Operations. Digital Ocean would require a lot of extra Developer Operations overhead writing build and deployment scripts using their APIs. AWS integrates with Bitbucket and Github - two services we use for source control. AWS also offers managed build and deployment services that Troll Purse will be leveraging. Architecture Digital Ocean is limited to Droplets. This pales in comparison to AWS's robust EC2, ECS, or serverless services. In AWS, Troll Purse can decouple services and code for various solutions. To do the same distributed computing in Digital Ocean as Troll Purse is enabled to do in AWS would require a significant investment in architecting infrastructure. In AWS, this is vastly done for Troll Purse - once an architecture is designed, only configuration of the services need to be done. With Digital Ocean, Troll Purse had to setup an Nginx server for serving static website content. It did not scale well (without more invested time in building an architecture and more configurations) as all content was stored on that server. In AWS, Troll Purse can distribute web content using Cloud Front for CDN and server static content from S3. Totally serverless and decoupled from the blogging platform and the forum servers (the latter is yet to be deployed). Flexibility In Digital Ocean it was difficult to manage servers and logical groupings of services. AWS offers tagging of resources and services to help better monitor health, costs, and grouping of application services built and provided by Troll Purse. Unfortunately, Digital Ocean does not offer the robust services and infrastructure needed for a company built with development speed in mind. A lot of services need to be hand crafted, as well as servers. This slows development efforts and makes iterative development a nightmare. Using small, simple services each with a specific purpose in mind, Troll Purse is better able to develop an experiment based on the offerings provided by AWS. Digital Ocean only provides five real services, Compute, Object Storage, Block Storage, Networking, and Monitoring. Each of these are small in comparison to comparable AWS services such as Compute Services (ECS or EC2), Storage, Virtual Private Cloud (Networking), and Cloud Watch (Monitoring). In AWS each of those services / categories work with each other or are umbrellas to a myriad of other flexible offerings. Finally, AWS has way more to offer - just take a look! Future Plans After much discussion, Troll Purse concluded our future development needs will be implemented faster and scale naturally in AWS over Digital Ocean. While we enjoyed our brief experience with Digital Ocean, we are excited to build using AWS as our cloud and hosting provider. While we do not plan to build anything with Lumberyard now, we have some exciting projects that will easily leverage the power of AWS. Such projects are backend analytics of our games using Amazon API Gateway, Lambda, and data services. We also hope to build an integrated environment so that each game shares common interfaces into our development and publishing environment. Conclusion While this blog post is mostly given as an explanation of our tactical decision to migrate to AWS, Troll Purse hopes it will serve as guidance to future developers facing the same decisions. We found that AWS provides a lot for small and large companies when it comes to infrastructure, flexibility, and accommodating future plans. Originally posted on Troll Purse Dev Blog
  10. Introducing Myself

    Hello.   Thought I would say it due to the fact that most forums no one says hello back.   so - "Hello"
  11. Modeling Armor for a Fat Man

    Just had to pop in and say thanks for mentioning this. Never heard of this product and it looks like a great tool.
  12. Advice for LOD switching

    Are you using a particular game engine?    One thing that some engines support is the grouping of LoD hierarchies. This way a single building, made up of modular pieces - each with their own LoD, can be grouped together as a single LoD unit. That way, the LoD calculation is done on the group, but the LoD meshes are applied per mesh using the group's calculation. 
  13. Whats the added benefit of using hexadecimal?

    As others have said: it is based on the context of your use of the variable. If you mean to use it as a 'mask' or 'bitflags' (aka flags) hexidecimal or binary literals are easier to use and infer the meaning better than decimal values. If it is to be a value, the use decimal or integral values.
  14. Load all textures at once, or as needed?

    Do you have any source material for this? (I am an RTS fan and am always interested in code examples/discussions ESPECIALLY for the Total War series) 
  15. Getting over game programming perfectionism

      This is great advise. To keep yourself from going back to code chunks to perfect, great a list of acheivable goals that your code must do. This will get you to a working state as well as (magically) give you test cases. What I recommend is something similar to "agile user stories". They essential state a feature that your product must have, then there is a list of what must be done in order to mark that feature achieved. This will help you not only get away from perfectionism, but also write code that meets what you need and nothing more. 
  • Advertisement