Jump to content
  • Advertisement
Sign in to follow this  
  • entries
    7
  • comments
    0
  • views
    9959

Unreal Posting Trello Cards from a Running UE4 Game

ShawnGreer

914 views

UE4-Trello.jpg

This tutorial will give a step-by-step guide for creating Trello cards from inside a running Unreal Engine 4 project. While you can use this for just about anything you can think of, I personally use this as a way for my players to post bugs from inside the game. If you're not familiar with Trello, I highly suggest you check it out. It's a great tool for project management and organization. It should go without saying, but you're going to need an account on Trello in order to make any progress here.

I also want to mention at this point that this will be a C++ tutorial. Without some form of additional plug-in or other engine modification, you can't make the needed HTTP Request calls. Also, setting this up will assuming you know how to add input mappings and have worked with Widgets a bit.

Note: This was created UE4 4.17.

This article was originally published Ding! Games.

We're going to start simple, but I'll talk about a few ideas for adding in some variance toward the bottom.

Part I: Trello

We're going to start by going through some steps to authorize access to your Trello account and get the information we need in order to post new cards onto the board of your choice. In the end, we're looking to make an HTTP request to Trello that looks something like this (the italicized parts surrounded by {} are what you'll end up replacing with your own values).

https://api.trello.com/1/cards?key={Your+key}&token={yourtoken}&name={New+card+name}&desc={New+card+description}&idLavels={Label}&idList={list}

Step 1: Key

The first thing to do is generate a key with your Trello account. This will let you start to get the rest of the required items. While logged into Trello, go to the following link:

https://trello.com/app-key

Step 2: Token

The next step is to authorize write access. Copy the below address, but replace {Your+Key} with the key you got from Step 1 (make sure you take out the {} as well).

https://trello.com/1/authorize?key={Your+Key}&scope=read%2Cwrite&name=My+Application&expiration=never&response_type=token

Step 3: Board Id

Now you need to get the id of the actual board you want your cards to get posted to. Use the following link (no modifications needed):

https://trello.com/1/members/me/boards?fields=name

Exactly how this looks will depend on which Web Browser you're using. For example, Chrome will just spit out all of the text without organizing it at all, while Firefox will sort and color the data for you. Either one is fine, just look for the long string of alpha-numeric characters right after the name of the board you're specifically looking for.

This id isn't actually needed in the HTTP request line, but you need it to get the next two ids...

Step 4: List

Next up, the list within the board you want the cards to post to. As before copy the below, but replace the board id with the id you got from Step 3.

https://api.trello.com/1/boards/{board+id}/lists

Again, how it breaks it out will be determined by what Web Browser you're using. This is where using something like Firefox will make it a lot easier to pick out the exact one you're looking for. This time, the id (not the pos) you need is before the name of the list you want to post to. Also, make sure you don't inadvertently copy the board id again.

Just one more...

Step 5: Label

Last, but not least, is the label you want put on the card itself when it gets posted. This should be getting familiar, replace the board id in the address with your own.

https://api.trello.com/1/boards/{board+id}/labels

As with step 4, this can be a bit messy if your browser doesn't break it out for you, but you should be getting the hang of it by name. Grab the id (first entry per set) for the color you want your bards to post as.

Okay, hopefully you copied all those ids down somewhere, because you're going to need them for the next part, actually writing out the code.

Part II: C++ Code

Now that we've got all of the various ids that we need, it's time to dive into doing some code.

Step 1: HTTP Module

First up, we need to make a small edit to the build file, and include the HTTP module.

public class Zeus : ModuleRules
{
	public Zeus(ReadOnlyTargetRules Target) : base(Target)
	{
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HTTP" });

		PrivateDependencyModuleNames.AddRange(new string[] {  });
	}
}

Step 2: Header Definition

Now, on to the actual implementation. I added my logic in the Game Mode, but you can actually put it just about anywhere you want. I'm also going to show a very simplistic setup, although I'll give some ideas on how to expand upon it at the bottom.

#pragma once

#include "ZeusFunctionLibrary.h"
#include "StandardPlayerController.h"
#include "Http.h"

#include "GameFramework/GameMode.h"
#include "ZeusGameMode.generated.h"

UCLASS()
class ZEUS_API AZeusGameMode : public AGameMode
{
	GENERATED_BODY()
	
public:
	AZeusGameMode();
	
	/** Used to report a bug */
	UFUNCTION(BlueprintCallable, Category = "Trello")
	void ReportBug(FString Name, FString Description);

	UFUNCTION(BlueprintImplementableEvent, Category = "Trello")
	void ReportBugComplete(bool bWasSuccessful);

private:
	void OnReportBugComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
	
};

There are three functions defined in the above code:
  1. ReportBug: My goal is to have bugs reported from the game's GUI, so I wanted to be able to have the actual reporting of the bug called from blueprints. This function is passing the Name of the bug, and the full description (both entered by the player). You can modify this as you see fit.
  2. ReportBugComplete: This is implemented more for testing purposed. It's used to push the result of the bug submission back up to Blueprints to be handled. You can modify this as desired, or just remove it entirely.
  3. OnReportBugComplete: This is the function that gets automatically called after your HTTP request is processed. As such, it has to stay as is.

Now to the best part, submitting the card!

Step 3: Implementation

Here's the code:

#include "Zeus.h"
#include "ZeusGameMode.h"

const FString TrelloCards = "https://api.trello.com/1/cards?";

const FString Key = "1234567890abcdefghijklmnopqrstuv";
const FString Token = "123456789abcdefghijklmnopqrstuvwxyz123456789abcdefghijklmnopqrst";
const FString Label = "123456789abcdefghijklmno";
const FString List = "123456789abcdefghijklmno";

AZeusGameMode::AZeusGameMode()
{
	// Nothing extra needed in the constructor for this
}

void AZeusGameMode::ReportBug(FString Name, FString Description)
{
	TSharedRef Request = FHttpModule::Get().CreateRequest();
	Request->SetVerb("POST");

	//Replace all the spaces with +
	Name = Name.Replace(TEXT(" "), TEXT("+"));
	Description = Description.Replace(TEXT(" "), TEXT("+"));
	
	//Construct the HTTP url
	FString URL = TrelloCards +
		"key=" + Key +
		"&token=" + Token +
		"&name=" + Name +
		"&desc=" + Description + "+(V:+" + UZeusFunctionLibrary::GetProjectVersion() + ")" +
		"&idList=" + List +
		"&idLabels=" + Label;

	// Set the built URL
	Request->SetURL(URL);
	// Bind OnReportBugComplete() to be called once the request is complete
	Request->OnProcessRequestComplete().BindUObject(this, &AZeusGameMode::OnReportBugComplete);
	// Process the request
	Request->ProcessRequest();
}

void AZeusGameMode::OnReportBugComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	// Calls up to a Blueprint implemented event
	ReportBugComplete(bWasSuccessful);
}

All of the logic takes place in the ReportBug function, essentially going through the following steps:
  1. Create the request and set it to POST (This is an HTTP protocol thing)
  2. Change all of the spaces to + (Another HTTP protocol thing)
  3. Build out the full string to be reported, using the model I posted way up top. Note that I have a function in there that adds on the current project version to the description. This is very handy for tracking bugs, as you really want to know what version someone is playing, without having to worry about them typing it in themselves somwhere.
  4. Set the built URL against the Request, bind the OnReportBugComplete() function and then process the request.

That's it for the code level. This alone gives you a good bit of freedom for what to do, but I'll show you a simple implementation at the Blueprint level next.

Phase 3: Blueprints

Step 1: Widget

First, lets build out our Widget that the player will actually use to input their bug:

Widget-Hierarchy.png

Bug-Report-Screen.png

I was looking for functional when I threw this together, not pretty. It's an extremely simplistic Widget, with some texts boxes for the user to input a title and description of the bug, and then either Submit, or Cancel.

The buttons have the following attached to their respective OnClick() events:

Submit:

Click-Submit-Button.png

Cancel:

Click-Cancel-Button.png

Step 2: Input Mapping

Under project Settings->Input, we want to add an action mapping to start of this whole series of events:

Action-Mapping.png

Step 3: Report Bug Event

Last, but not least, we need to fill out the logic for when the user actually presses F12. I put this in the Blueprinted version of my Player Controller, but you can put this in anything that receives user input:

InputAction-ReportBug.png

A couple of things to note here is that I automatically take a screenshot and pause the game. Neither or required, but I find both to be handy.

Conclusion

And there you have it! While running, you should now be able to press F12, create a bug report and submit it to Trello, where it should appear almost instantly.

Now, as I mentioned earlier, this is a rather straightforward approach that has the potential for expansion. For example, as it stands, the Board, List and Labels are currently hard coded values, meaning that you always post to the same place with the same label marking it. You could expand upon that by putting in a drop-down for the play to choose how critical of a bug they found, passing that as a variable down to the ReportBug() method (such as in the form of an enum), which you could then use to select which label gets attached.

You could also let the player submit recommendations or feedback in addition to bug reports, which could result in the cards being posted to a different list, or completely different board. The variations are endless, but ultimately, anything that lets the player quickly provide some form of feedback without having to leave the game or do something else is ideal.

Hopefully you don't have any trouble getting any of the above implemented, but if you do, please feel free to post your questions, and I'll see what I can do.

Best of luck!

Shawn

Shawn is the Designer/Developer at Ding! Games. They are currently working on a game code-named Zeus, a RPG Action/Puzzle game. For more great articles or to get the latest news bout game development, visit them at their site.



0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
  • Advertisement
  • Blog Entries

  • Similar Content

    • By somedev
      Hey guys, I'm pretty new to Direct3D and I try to generate a png image file to disk from a already loaded initialized image from `LPDIRECT3DTEXTURE9` object.
      Basically I'm creating a function that get's an std::string for the file destination path and get the pixels from the existing LPDIRECT3DTEXTURE9 object and save them to disk.
      Any help would be much appreciated!
      Thanks in advance!
       
      EDIT:
      I though I should add a sample code of how I do it with BitMap format to get the idea of what I'm trying to do.
      Note that this code works, but I try to do something similar with PNG format.
      int Width = 128; int Height = 128; LPDIRECT3DTEXTURE9 m_lpTexture; LPDIRECT3DDEVICE9 s_lpD3DDev = NULL; bool SaveToBitmapFile(const std::string & szPath) { LPDIRECT3DSURFACE9 lpSurfSrc = NULL; LPDIRECT3DSURFACE9 lpSurfDst = NULL; m_lpTexture->GetSurfaceLevel(0, &lpSurfSrc); s_lpD3DDev->CreateOffscreenPlainSurface(Width, Height, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &lpSurfDst, NULL); if (D3D_OK != D3DXLoadSurfaceFromSurface(lpSurfDst, NULL, NULL, lpSurfSrc, NULL, NULL, D3DX_FILTER_TRIANGLE, 0)) { lpSurfDst->Release(); lpSurfDst = NULL; lpSurfSrc->Release(); lpSurfSrc = NULL; } CBitMapFile myBmp; myBmp.Create(Width, Height); D3DLOCKED_RECT LR; lpSurfDst->LockRect(&LR, NULL, 0); for (int y = 0; y < Height; y++) { BYTE* pPixelsSrc = ((BYTE*)LR.pBits) + y * LR.Pitch; BYTE* pPixelsDst = (BYTE*)(myBmp.Pixels(0, y)); for (int x = 0; x < Width; x++) { pPixelsDst[0] = pPixelsSrc[0]; pPixelsDst[1] = pPixelsSrc[1]; pPixelsDst[2] = pPixelsSrc[2]; pPixelsSrc += 4; pPixelsDst += 3; } } lpSurfDst->UnlockRect(); lpSurfDst->Release(); lpSurfDst = NULL; lpSurfSrc->Release(); lpSurfSrc = NULL; HANDLE hFile = ::CreateFile(szPath.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); DWORD dwRWC = 0; WriteFile(hFile, &myBmp.m_Header, sizeof(myBmp.m_Header), &dwRWC, NULL); WriteFile(hFile, &myBmp.m_InfoHeader, sizeof(myBmp.m_InfoHeader), &dwRWC, NULL); int iWidth = myBmp.Pitch(); for (int y = myBmp.m_InfoHeader.biHeight - 1; y >= 0; y--) WriteFile(hFile, (BYTE*)myBmp.m_pPixels + y * iWidth, iWidth, &dwRWC, NULL); CloseHandle(hFile); }  
    • By tspartant
      Hey everyone! My name is Ryan and I am the founder of Visualistic Studios, LLC. 
      I'm looking for experienced developers interested in short term and long term contracting. For the past 3 years I've been working in game development contracting, and the past year I've been working full time from home. Since then, I've received more and more contracts and I'm now at the point that I have too many for myself to handle. I have at least another full time job's worth of programming offers, and around 30-80 hours a month for 3D modeling/animation.  I have contracts using Unity as well as Unreal Engine 4, so if you can program in either please contact me. 
      If you are interested in working on these contracts, please send me links to your work and you hourly rate. (Most contracts range be between 18-25$/h, but please provide your normal hourly rate)
      You can get ahold of me through email - "ryan.hobbs@visualisticstudios.com", or Discord "TSpartanT#4670"
       
      Thank you everyone for reading, hope to hear from you soon!
    • By mako737
      Mário Pauko 1 Hello, when i create a project with UE4 and c++ i get this error
      LINK : fatal error LNK1181: cannot open input file 'C:\Users\Admin\Desktop\Môj Život\UE_4.20\Engine\Intermediate\Build\Win64\UE4Editor\Development\Core\UE4Editor-Core.lib' Error executing C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.15.26726\bin\HostX64\x64\link.exe (tool returned code: 1181) UE4Editor-akNejdeMamPici.lib (0:02.31 at +1:27)    Creating library C:\Users\Admin\Documents\Unreal Projects\akNejdeMamPici\Intermediate\Build\Win64\UE4Editor\Development\akNejdeMamPici\UE4Editor-akNejdeMamPici.lib and object C:\Users\Admin\Documents\Unreal Projects\akNejdeMamPici\Intermediate\Build\Win64\UE4Editor\Development\akNejdeMamPici\UE4Editor-akNejdeMamPici.exp ---------------------- Done ----------------------     Rebuild All: 0 succeeded, 1 failed, 0 skipped ERROR: UBT ERROR: Failed to produce item: C:\Users\Admin\Documents\Unreal Projects\akNejdeMamPici\Binaries\Win64\UE4Editor-akNejdeMamPici.dll        (see ../Programs/UnrealBuildTool/Log.txt for full exception trace) Total build time: 118,54 seconds (XGE executor: 0,00 seconds)  
      Pls tell me every step i have to do for this problem 
       
       
    • By _Nyu
      Hello,
      I'm trying to make a PBR vulkan renderer and I wanted to implement Spherical harmonics for the irradiance part (and maybe PRT in the future but that's another story).
      the evaluation on the shader side seems okay (it look good if I hardcode the SH directly in the shader) but when I try to generate it from a .hdr map it output only gray scale.
      It's been 3 days I'm trying to debug now I just have no clue why all my colour coefficients are gray.
      Here is the generation code:
       
      SH2 ProjectOntoSH9(const glm::vec3& dir) { SH2 sh; // Band 0 sh.coef0.x = 0.282095f; // Band 1 sh.coef1.x = 0.488603f * dir.y; sh.coef2.x = 0.488603f * dir.z; sh.coef3.x = 0.488603f * dir.x; // Band 2 sh.coef4.x = 1.092548f * dir.x * dir.y; sh.coef5.x = 1.092548f * dir.y * dir.z; sh.coef6.x = 0.315392f * (3.0f * dir.z * dir.z - 1.0f); sh.coef7.x = 1.092548f * dir.x * dir.z; sh.coef8.x = 0.546274f * (dir.x * dir.x - dir.y * dir.y); return sh; } SH2 ProjectOntoSH9Color(const glm::vec3& dir, const glm::vec3& color) { SH2 sh = ProjectOntoSH9(dir); SH2 shColor; shColor.coef0 = color * sh.coef0.x; shColor.coef1 = color * sh.coef1.x; shColor.coef2 = color * sh.coef2.x; shColor.coef3 = color * sh.coef3.x; shColor.coef4 = color * sh.coef4.x; shColor.coef5 = color * sh.coef5.x; shColor.coef6 = color * sh.coef6.x; shColor.coef7 = color * sh.coef7.x; shColor.coef8 = color * sh.coef8.x; return shColor; } void SHprojectHDRImage(const float* pixels, glm::ivec3 size, SH2& out) { double pixel_area = (2.0f * M_PI / size.x) * (M_PI / size.y); glm::vec3 color; float weightSum = 0.0f; for (unsigned int t = 0; t < size.y; t++) { float theta = M_PI * (t + 0.5f) / size.y; float weight = pixel_area * sin(theta); for (unsigned int p = 0; p < size.x; p++) { float phi = 2.0 * M_PI * (p + 0.5) / size.x; color = glm::make_vec3(&pixels[t * size.x + p]); glm::vec3 dir(sin(phi) * cos(theta), sin(phi) * sin(theta), cos(theta)); out += ProjectOntoSH9Color(dir, color) * weight; weightSum += weight; } } out.print(); out *= (4.0f * M_PI) / weightSum; }  
      outside of the SHProjectHDRImage function that's pretty much the code from MJP that you can check here:
      https://github.com/TheRealMJP/LowResRendering/blob/2f5742f04ab869fef5783a7c6837c38aefe008c3/SampleFramework11/v1.01/Graphics/SH.cpp
      I'm not doing anything fancy in term of math or code but I that's my first time with those so I feel like I forgot something important.
      basically for every pixel on my equi-rectangular hdr map I generate a direction, get the colour and project it on the SH
      but strangely I endup with a SH looking like this:
      coef0: 1.42326 1.42326 1.42326
      coef1: -0.0727784 -0.0727848 -0.0727895
      coef2: -0.154357 -0.154357 -0.154356
      coef3: 0.0538229 0.0537928 0.0537615
      coef4: -0.0914876 -0.0914385 -0.0913899
      coef5: 0.0482638 0.0482385 0.0482151
      coef6: 0.0531449 0.0531443 0.0531443
      coef7: -0.134459 -0.134402 -0.134345
      coef8: -0.413949 -0.413989 -0.414021
      with the HDR map "Ditch River" from this web page http://www.hdrlabs.com/sibl/archive.html
      but I also get grayscale on the 6 other hdr maps I tried from hdr heaven, it's just different gray.
      If anyone have any clue that would be really welcome.
    • By FonzTech
      Hi! I have a doubt about making a "Room" manager. For "room", I mean a collection of game objects (in detail, I would say "a collection of subclasses of GameObject"), with their own properties (position, rotation, scale and other specific ones).
      For now, I only want to load a "room" from an external file, then instantiate and setup game objects based on its data.
      I thought about making a simple text file, containing a big structure like an array, where each entry, contains the class to be instantiated (along with their properties).
      I'm not very skilled in C++. In Java, I would load classes by their name using reflection (Take the security thing apart, for now), then set their properties (always using reflection), based on the data contained in the above text file.
      Think like a JSON, where you have a structure like this:
      [ { "type": "Player", "general": { "position": "1,2,3", "rotation": "1,2,3", "scale": "1,2,3" } "specific": { "someSubclassField": "myValue", "anotherPlayerField": "myValue2" } }, { "type": "MainMenu", "general": { "position": "0,0,0", "rotation": "0,0,0", "scale": "1,1,1" } "specific": { "isLevelEditorEnabled": true } } ] There is some mechanism I can adopt to avoid setting-up game objects at compile-time?
      I would like to avoid the myriad of if statements.
      Any advice is appreciated. Thanks in advance!
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!