The main page for UnrealOni can be found HERE.
- 1 Oni 2 game
- 2 Oni 2 game tech demo with Unreal Engine 4
- 2.1 Goal
- 2.2 Stuff that is probably coverd by the tech demo
- 2.3 Practice notes
- 2.4 Tutorial summary: 3rd Person Game
- 2.5 Selected knowledge
- 2.5.1 Code management
- 2.5.2 Game instance
- 2.5.3 Main func
- 2.5.4 Cameras
- 2.5.5 Music
- 2.5.6 Meshes
- 2.5.7 Sockets
- 2.5.8 Powerups
- 2.5.9 HUD
- 2.5.10 Tables and structures
- 2.5.11 Widgets
- 2.5.12 Animations
- 2.5.13 Normal maps
- 2.5.14 BSL equivalents
- 2.5.15 Cooking the game
- 2.5.16 Exit the game
Oni 2 game
If we had a real option to create an Oni 2 with modern engine I would root for Unreal Engine 4.
It works on PC and Macs and it is free for non-commercial projects.
There's a big community, documentation and tutorials.
If the demo is going to be extended it would be better to work with original/free content from begin with, sparing us IP concerns. Old content is meant as placeholders.
It supports weapon and h2h combat. Of course Oni's unique fighting style would have be re-written. But everything else should be supported by the engine. Particle, trigger volumes, wide environments, details characters. What else could you wish for?
Besides new levels, characters and story... How about new gameplay elements? Since the Daodan is an core element of the story it would be shame not to make use of it. It would add a strategical component and adds to the replay value.
Since the bio aspects would be hard to reinvent for a spiritual successor, one might more concentrate on good 'ol nanotech. "Omega days"? "Iron demons?"
Primary Daodan traits
- Healing (stopping a heavy bleeding and recovery speed)
- Power (stronger attacks, throwing or using heavy objects and enemies as weapons (e.g. using a skriker as Nunchaku with maxed out power))
- Speed (faster attacks, running, reflexes (dodging, counters))
- Adapting (mutation speed (biological computing by means of "Oni cluster" cells))
- Mechanical resistance (adaptions to pressure and kinetic damage (e.g. graphene enhanced cells))
- Physical resistance (VDG, plasma, phase stream projectors (electric isolation))
- Chemical resistance (acid, poison (lead bullets, mercury bow bullets))
- Biological resistance (screamer cells, infections by untreated wounds)
- Thermal resistance (extreme heat - fire, extreme coldness)
- Nuclear resistance (depleted uranium ammunition and surviving contaminated areas)
Basically you can "tank up" against weapons and don't need to worry about ammo.
To keep balance you could add more dangerous weapons as the game progresses.
Secondary Daodan traits
The Daodan is not almighty. It has certain weaknesses you should watch out for.
- Your Daodan abilities depend on Daodan biomass which means you can either unlock new abilities or enhance existing ones. And there's a limit to your abilities, because your cell count is limited.
- There should be one or two "ultimates" in Imago stage that depend on your evolution.
- Your number of Daodan cells depend on taken damage and recovery that happen in the last level.
- But too much damage can cause too high growth rates (temporary sudden pain and fever resulting in moments of reduced vision, shooting accuracy, "getting blocked" rate) or it simply kills you because you exceeded the maximal healing capacity of the daodan.
- Sytropin can help you in lowering growth rates. Be aware of increasing drug resistance as the game progresses.
- Reverting an existing mutation takes more "work" than establishing a new one. Basically your course of action is directed evolution.
Improved movement set and h2h
- prone mode
- sneaking at walls and through vent channels
- throw foes over obstacles
- extend h2h with Parkour elements (e.g. wall jump connectable to a flying knee)
- use pickable objects as weapons (either throwable or usable as a club)
- ledge grab (not a must-have)
- swimming (not a must-have)
- controllable drones, robots, mechs, vehicles
- dynamic lighting, wearable lights (flash lights) for dark environments
- temperature affects characters (not a must-have)
- emotions affects characters (not a must-have, rage enhances Daodan powers to a limited degree)
Oni 2 game tech demo with Unreal Engine 4
There is big skepticism inside the community whether an Oni-like game can be accomplished with the Unreal 4 engine.
Therefore I seek to create a technical demo to proof feasibility of such project.
The availability of XML documentation for Oni 1 seem to have speeded up mod creation so I will pursue a similar route for this project. The documentation written here in the process shall serve as measure to lower the learning curve of other modders (or then programmers / content creators).
Stuff that is probably coverd by the tech demo
Everyone could pickup one of these points and investigate how to implement it.
If successful that person would write down a mini-tutorial here on OniGalore so others could see progress and learn from the documentation to perhaps pickup the next, somewhat more complex point.
There could be project files on a cloud drive as long as there aren't too many people involved (otherwise a SVN). New files could be uploaded into a revision folder. Users could test the changes and then update the project in the cloud.
- V - video
- T - tutorial
- D - documentation
Features the demo should include:
- two levels (to test normal level load routine and level streaming (T))
- skydome (V)
- main menu
- So far I failed to add background music. This gave rise to the idea of having an own level for the main menu where you can spawn ambient music. Muro or another character could be previewed with their current movement set and equipment. Shooting the camera would make the game quit. This will be useful when the enemy takes over this HQ AI and he needs to shut it down. "Mission failed." "Screw you!" Pressing the "V" key would allow you to go into your training room or HQ testing new moves or doing investments.
- HUD ("circular" health bar, item slots)
- background music (D) (T) (V)
- save game state
- read game state
- text pages for diary, etc.
- fbx import of old objects
- animated objects with collision box to stand on
- objects that can be thrown
- CJBO equivalents (characters, console, door, trigger (laser), trigger volume, ...)
- compass (objective)
- FBX import and usage of old characters
- combining character parts to one model
- rigging the model
- minimum TRAC-TRAM integration I (walk cycle and idle)
- prone mode
- keyboard and mouse controls
- functional animations
- character h2h (force-sensitive) (a.k.a physical animation component)
- character h2h
- hit reaction
- minimum TRAC-TRAM integration II (combat idle, punch, kick, block, half-damage, unblockable, super)
- minimum TRAC-TRAM integration III (throws)
- minimum TRAC-TRAM integration IV (throwable objects)
- overlay animations
- h2h weapons
- ledge climbing
- cover (AI)
- healing by hypo and level areas
- particle (attachable to env, weap, char)
- weather (rain, snow, wind, dust, lightning)
- two weapons
- ammo type: ballistic ammo
- ammo type: energy cell
- patrol paths and pathfinding
- recreation of basic ONCC properties (incl. shooting inaccuracy)
- spawn AI
- h2h-specific animations
- got hit animation
- got hit animation (CBPM-specific)
- throws mechanics
- follow me command, stop command
- alarm behavior
- CBPM-specific bullet hit behavior
- add effects via ONIE-TMBD-MTRL mini complex
- driving a truck
- BSL equivalent ("Blueprint")
- team support (easy methods to make project contributions)
- levels of difficulty
- Easy: -20%? health for foes
- Hard: +20%? health for foes, foe class upgrade?
- Extreme: shorter reaction times of AI ("killmequick")
- powerups on the ground can be destroyed quite easily by projectiles or heavy h2h attacks
- ammo -> blast damage and fire
- cell -> blast damage and stun effect
- hypo -> little blast damage and a cloud of healing gas
- shield -> major blast damage
- invis -> blinding
- LSI -> mission failed ?
- powerups on the ground can be destroyed quite easily by projectiles or heavy h2h attacks
- YOLO: Extreme mode plus when player dies he cannot reload that savegame. He has to restart at level 1.
- YOLO +: YOLO plus the games has never been played before. first play after installation. This is a secrete achievement.
Besides from what all should be tested and made, here's a groundwork tutorial: Blueprint 3rd Person Game
It touches pretty much stuff in those 4 hours: getting a character into a level, setting him up as player, the controls, animations cycles, animation blending, animation overlay and attaching physics
You could probably split this thing into three parts: learning the basics, adding stuff from the long feature list (those not already covered by the basics), and tweaking animations controls to behave like Oni 1
Now then if you want to try this do the following.
- For content creation you maybe want to use Blender since it is free and available for PC, Mac and Linux.
- Install Visual Studio 2017 Community (to write C++ later)
- Register at https://www.unrealengine.com/en-US/, while logged in you can download the installer from the upper right corner. (Blue button "DOWNLOAD".)
- Start Installer and download Unreal Engine 4.17.1 (It will be about 6 GB. It extracts to 17 GB.)
- Learn viewport navigation from the yellow flashing blue hat icon.
- Then continue with the videos of the 3rd Person Game tutorials.
Tutorial summary: 3rd Person Game
It seems like there aren't scripts anymore. Blueprint is Visual scripting, you are working with visualized functions, objects, etc. It's more what you can do with the C++ code which also gets compiled.
FBX can contain data for TRBS, TRIA, TRAC, multiple TRAM, materials, textures.
- Video ?
- Blend Parameter now on the left side.
Keybinding (equivalent to key_config.txt)
Input to move binding (equivalent to StNA)
Support for Keyboard, Mouse (and inverted control), Gamepads and touch
Player camera settings editable (distance holder named "Spring Arm")
- Animation cycles (TRAM - shortcuts)
- Transition rules (similar to those in TRAM headers <FromState>, <ToState>)
- Blend Space is an heavily enhanced version of <Interpolation>
- The animation gets adapted in length AND rotation values. When properly used, transition are nothing less than perfect.
- Video 8
- Anim Preview Editor is on the right now
- Event Graph: right-click for context menu to add missing "Try Get Pawn Owner"
- Video 12
- Character Blueprint is sort of an ONCC. They aren't exactly equal.
- It misses seeing, hearing, weapon skills and more. It has settings for jump, "air control", LOD, shadow, few CHAR events, collision box, tags, ONIE related stuff
EventGraph -> Component -> Mesh -> e.g. Get Material Index
Whenever you edited a Blueprint: compile (F7) and save (Ctrl + S).
In order to sort things and avoid code duplication I'm trying to spit things for now the following way:
- game related code such as a level loading function and menu references goes to the Game Instance
- level related code such as a Slash Screen function goes to the Level blueprint
- player related code such as keyboard inputs go to a custom Player Controller (changeable in the Game Mode)
- AI related code goes to the ONCC component blueprint
When we store information in an game instance it won't disappear after loading the next level.
Let's say there is a menu button or hotkey in a pause menu widget to disable hint texts.
Using variables in a widget is a dead end. They are destroyed as soon as we close the widget.
We could save our choice of showing the hint text in the level blueprint but as soon as we proceed to the next level the hint text would be shown again.
So, we go up in the hierarchy and store the information in the game instance.
To make the information really persistent (between game starts or when loading a save game) all we would need to do is saving the information from the game instance to a save game.
Equivalent to Oni's main func can be found / created in the "Level Blueprint".
Blueprints > Open Level Bluprints
Right-click to open context menu. Search for and add node "Event BeginPlay"
At this point it's empty.
Maybe for testing or learning purposes add another node: "Play Sound 2D".
Change its Sound source to a Sound Cue you made.
Connect the nodes Event BeginPlay with Play Sound 2D.
You can copy-paste blueprints and share them with others in plain text.
Example of an exit function. (In the actual game use Escape key to exit.)
Begin Object Class=/Script/BlueprintGraph.K2Node_InputKey Name="K2Node_InputKey_0" InputKey=Escape NodePosX=864 NodePosY=-832 NodeGuid=99076AC847F4C36E70B54AA78927F135 CustomProperties Pin (PinId=92DEBC9A43D657DB995617849A0B7927,PinName="Pressed",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsMap=False,PinType.bIsSet=False,PinType.bIsArray=False,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,LinkedTo=(K2Node_CallFunction_44 BC9E715842362EDD413EF8A39B14F625,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) CustomProperties Pin (PinId=3CDB2A9D40427A0CCFDABF8F090CE41F,PinName="Released",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsMap=False,PinType.bIsSet=False,PinType.bIsArray=False,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) CustomProperties Pin (PinId=02AB461F46584F140257A5BC03E90865,PinName="Key",Direction="EGPD_Output",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'/Script/InputCore.Key',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsMap=False,PinType.bIsSet=False,PinType.bIsArray=False,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) End Object Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_44" FunctionReference=(MemberParent=Class'/Script/Engine.KismetSystemLibrary',MemberName="QuitGame") NodePosX=992 NodePosY=-832 NodeGuid=F926936F4422AC469B38B3B6EBAB12BE CustomProperties Pin (PinId=BC9E715842362EDD413EF8A39B14F625,PinName="execute",PinToolTip="\nExec",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsMap=False,PinType.bIsSet=False,PinType.bIsArray=False,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,LinkedTo=(K2Node_InputKey_0 92DEBC9A43D657DB995617849A0B7927,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) CustomProperties Pin (PinId=308342D741456EB59283E7A1B0C7183D,PinName="then",PinToolTip="\nExec",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsMap=False,PinType.bIsSet=False,PinType.bIsArray=False,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) CustomProperties Pin (PinId=2EF7797B47E79A543E0AEAB9F7914F98,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nKismet System Library Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'/Script/Engine.KismetSystemLibrary',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsMap=False,PinType.bIsSet=False,PinType.bIsArray=False,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,DefaultObject="/Script/Engine.Default__KismetSystemLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) CustomProperties Pin (PinId=E5668388474ABD86D0C505AE22C65ECB,PinName="WorldContextObject",PinToolTip="World Context Object\nObject Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'/Script/CoreUObject.Object',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsMap=False,PinType.bIsSet=False,PinType.bIsArray=False,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) CustomProperties Pin (PinId=8E90ECF5471A7E7EA51527B95CA238F9,PinName="SpecificPlayer",PinToolTip="Specific Player\nPlayer Controller Object Reference\n\nThe specific player to quit the game. If not specified, player 0 will quit.",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'/Script/Engine.PlayerController',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsMap=False,PinType.bIsSet=False,PinType.bIsArray=False,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) CustomProperties Pin (PinId=FDCA728E4AFA5A2ABEA52A88B5B79FEA,PinName="QuitPreference",PinToolTip="Quit Preference\nEQuitPreference Enum",PinType.PinCategory="byte",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Enum'/Script/Engine.EQuitPreference',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsMap=False,PinType.bIsSet=False,PinType.bIsArray=False,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,DefaultValue="Quit",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) End Object Begin Object Class=/Script/UnrealEd.EdGraphNode_Comment Name="EdGraphNode_Comment_7" NodePosX=816 NodePosY=-880 NodeWidth=490 NodeHeight=230 NodeComment="Exit" NodeGuid=92DC2D0B492925CE0957EB865A535520 End Object
Third Person View
Right after testing the vanilla Third Person Character template in UE you will notice that the camera can be fully moved around the player character.
In Oni the orbiting is limited. IMO this added to Oni's unique vibe. Hand-to-hand combat felt more realistic because you can't really see behind your back.
- Part I
To reproduce the same behavior you have to silent the mouse controls on some conditions.
In theory the core function would hold these parts:
- check for camera's rotation relative to the character (if rotation exceeds limits the input gets nullified)
- check for left turn mouse rotation to get out of camera's right side maximum
- check for right turn mouse rotation to get out of camera's left side maximum
In practice I tweaked the facing value so that is always below 180 degrees (e.g. -270 becomes 90). That way following code only needs the later 2 of 3 checks.
https://1drv.ms/t/s!Asn4fV_yvODigUAfQ-spWYnvi1pV (dead link)
- Part II
During the BeginPlay Event you can set nodes to limit the camera pitch (up and down movement).
https://1drv.ms/t/s!Asn4fV_yvODigT8xJ35jXaxFlD35 (dead link)
- Part III
Oni's second unique feature of its Third Person View is the look mode. It shifts the mouse rotation control from the body to the head.
By that you can look to the side while keep running forward.
- Part IV
The limited mouse controls feel clumsy as long as the character can turn around by pressing the "walk backwards" key.
We need changes so that the character actually walks backwards and does NOT turn around.
Sound files extracted with OniSplit aren't compatible, you have to convert them to 16-bit PCM wav again. Audacity works well for that.
The OSBD properties can be recreated by turning the sound into a cue and then adding BluePrint logic to it.
UE4 is unable to seamlessly loop sound parts which is a requirement for playing permutations. However, almost every Oni modder imports complete soundtracks, so it's not that much of a loss.
Import two wav files to the content browser. Right-click one to "Create Cue". Double-click that created Cue.
Add "Wave Player", "Enveloper", "Random", and "Looping" nodes.
Set a wave file for each new Wave Player.
Select all and press "C" to group them in a comment. That way you better remember what it does and can move the stuff around all at once.
Since OniSplit outputs 3D meshes mostly as Collada (DAE) - and few as Wavefront (OBJ) - you cannot transfer objects from Oni to UE4 directly.
The CJBO files include a furniture file. But this can be mostly garbage - or empty. Instead the furniture is baked into AKEV. The meshes have flags which allow OniSplit to extract these objects again.
Onisplit -extract:dae level1_Final level1_Final/AKEV*.oni
In the past this created a bunch of DAE files:
EnvWarehouse_bnv.dae EnvWarehouse_cons.dae EnvWarehouse_door.dae EnvWarehouse_furn.dae EnvWarehouse_env.dae EnvWarehouse_env_markers.dae EnvWarehouse_script_X.dae EnvWarehouse_trig.dae other.dae
At some point _furn might be extracted as part of _env. The cause for this is unknown.
The scaling of OniSplit DAE files is too small. Use a factor of 10 to fix it. If we wanted to do a mass-import we would need a tool or a Blender script that alters the DAE (XML) data in advance. (Or maybe some python magic for the UE4 editor can help here too.)
Warehouse boxes from level 1 (and others objects) do only have 5 sides. Writing a macro for XSI/Blender COULD possibly add a 6th side automatically.
Placing individual furniture assets into levels
After dragging the assets into the level we need to reposition them all to 0, 0, 0 as the pivot point is used inside the FBX to save their relative position the the level.
Optional: Asset library
Besides re-imports of furniture, objects could be added to a new asset library. For the later case resetting the pivot point of meshes (V) can be useful.
If we would import an entire furniture file to UE4 it would create the objects as they are: for each object one mesh. That is inefficient as many objects are simply duplicates, although textures and materials are created only once.
To bypass this inefficiency scripting is needed.
Ending numbers such as "_7412" seem to be the CJBO id. (See AGQG <ObjectId>)
For example a script would export the furniture "U_freight_wht_long" and save the position of all instances to a text file. UE4 would need the import the FBX once and read that text file to place them all in the level. The problem is that rotations are all reset to 0. It might be possible to define some kind of reference and read the first vertex of each object, reading the different rotation and saving this to the text file as well.
A text file (txt/csv/json/xml) might store this data:
<asset name="U_freight_wht_long" cjboId="7412" pos="x,y,z" rot="x,y,z" /> <asset name="U_freight_wht_long" cjboId="7413" pos="x,y,z" rot="x,y,z" />
If we import a level 1 furniture file as it is, it creates 366 meshes, 66 textures/materials, resulting in a total 498 "uassets" 45 MB in disc space. The process takes all cores to get the job done, first import, about one or two minutes on a modern CPU.
Some possibilities of preparing FURN
|XSI scripting||considered "deprecated" by community|
|Standalone python||splitting DAE (mostly XML)|
|UE4 editor scripting||importing XML/CSV list for instances of meshes|
Adding door locklight particle to assets?
Some objects such as door locklight are meant to have particles. Oni happened to store some particle in ONLV, however the door locklight have been moved to the CJBO particle collection. That's something to keep in mind. If we want to add particle to the static meshes we would need to turn them into full-fledged blueprint assets.
ONLVlab.xml (locklight particle example)
<ENVP id="6"> <Particles> <ENVPParticle> <Class>locklight</Class> <Tag>lock1_locklight01</Tag> <Transform>-1 -4.559326E-08 -1.34529964E-05 1.34529973E-05 -2.03512554E-05 -1 4.53194744E-08 -1 2.03512554E-05 643.527 13.0480433 -275.8361</Transform> <DecalScale>1 1</DecalScale> <Flags></Flags> </ENVPParticle>
Green collision boxes
Although the green collision boxes are imported with transparency they are not completely invisible. We could either:
- remove the collision box (scripting)
- then tick checkbox in FBX import dialog "Auto Generate Collision"
- modify the texture "_marker_barrier"
- modify the material "MaterialLibrary__marker_barrier"
- change Opacity from Texture Sample to a scalar or vector 3 node (0,0,0 for black = fully transparent)
Since in Oni the green collision box is meant for characters how do we deal with particle collisions?
Custom collision boxes
In an 3D editor create collision objects and name it beginning with "UCX_". Save objects and collision objects together in one FBX file.
On import, uncheck "Auto Generate Collision".
Reimports (AKEV core geometry)
IIRC, Edt said you need to carefully pick the options while importing core geometry as rooms with otherwise open doors can be blocked for character locomotion.
For destructive environment / Chaos system a modular core geometry might be better. However, this would require scripting again to split geometries apart.
In XSI we can relative easily see where an object has "polygon clusters" and associated textures. (Select a polygon cluster in wireframe mode while having an Image panel open.)
Whether this can be an alternative to Edt's workflow only major testing can show whereby the geometry is extracted as usual...
Onisplit -extract:dae level1_Final level1_Final/AKEV*.oni
... and loaded into an 3D editor ...
A rough splitting could be done by looping over the polygon clusters
' XSI (VBS) scripting for each n in selection(0).activeprimitive.geometry.clusters logmessage (n.name) next
' basic test with Polygon75 instead of n.name SelectObj "world.polymsh.cls.Polygon75" SelectMembers "world.polymsh.cls.Polygon75" ' poly arg can be blank, in that case active selection is taken ' ExtractFromComponents "ExtractPolygonsOp", "world.poly[12653-12730]", "polymsh_extracted", , siPersistentOperation, siKeepGenOpInputs ExtractFromComponents "ExtractPolygonsOp", , "polymsh_extracted", , siPersistentOperation, siKeepGenOpInputs
After the level has been broken down into pieces it could be save as DAE and then converted to FBX.
At unknown constellation OniSplit outputs the "_env" file with furniture from level one (Syndicate Warehouse). This needs double-checking. Is a newer OniSplit or .NET/Windows 10 version to blame?
You can't socket static meshes. Set them to be "movable".
For now each socket will need its own bool variable to keep track whether it is in use or not.
Problem: when we use this node "get overlapping actors" then also actors stored in sockets will be counted. We would need to subtract the ones from the others. To differentiate the items we could check for a new variable "UsedInSocket". If the number is higher than -1 we can subtract it from the overlapping items because we already have this one collected.
Tested in 4.18
If powerups don't spam the area they should fall to the ground after use. They should be throwable and kickable to attract the attention of foes.
Maybe there should be a weight penalty to motivate h2h combat. As less items you carry around as faster, powerful and agile you are. Reminded to Ultra saiyan vs Super saiyan?
- All powerup "glowtex" textures has been recreated in higher resolution.
- Pickup materials use emissive color to be actual "glowtex".
- Pickup event executes a preflight to check for valid objects. For example it will determine whether your pickup slots for hypos are all full or not. In a second step only one of the valid pickups is added to the inventory.
- Slots in the character skeleton will be used for further experiments. Pickups will be stored in belt and chest pockets. (Short one-hand weapons (e.g. pistols) are going to be holstered at the legs while rifles are put on the back.)
- Powerups in sockets are scaled down and their glowtex becomes disabled.
- If powerups use the same names for their inner components (TV, glowtex plane, item mesh) then the code could maybe be simplified with an "interface blueprint" which take some inputs and gives them to all powerups that are bound to that interface. That would be useful when adding more items and weapons.
In Powerup BP: Custom event with boolean input parameter
- Set Visibility (New Visibility) (target: glowtex Plane object)
- Static Mesh Component-> AddLocalOffset
- Powerup Item -> Add local Rotation, Set World Scale 3D
In order to indicate ammo state of weapon a dynamic material can be used.
In this attempt the glow texture was applied to a plane that is attached to the hypo mesh. Right now the actual hypo mesh is just a placeholder made of a cuboid with green ellipses. The transparency seemed to make it darker than expected so I added some input to the material's emissive color. See gallery below.
In the following there is code that let's you pickup Hyposprays (1 + 2) and use them to regain health (3 + 4).
Caution: This script is different from the others as the the pickup key press event is located in the level blueprint. Normally key press events are found in the the character blueprint. We will probably want to change that in a future version though I don't know how yet. --paradox-01 (talk) 21:03, 16 December 2017 (CET)
If you add a trigger volume to your hypospray object it will act like a new collision box. This can be used to adds some tolerance to the pickup distance. Otherwise the character has to be not just really close but to actually overlap the object.
- new hypo object mesh
- pickup animations (kneel and take, take, "roll-take")
- condition to prevent pickup with certain animations
- new sounds ("cannot pickup more", "usage")
- health bar interaction
- flashing animation
- start red health area with a higher health value
- improve code where HypoRegenerationRate adds in to avoid rounding errors
Tested in 4.18
Observation from Oni 1 vanilla health bar. If base health is 200 points...
- remaining health is flashing at 60 points every 2s one time
- remaining health is flashing at 1 point every 1s two times
- critical health sound loop at 39 points (minimum sound volume)
- critical health sound loop at 1 point (max. sound volume)
- health drain animation from 200 to 1 health points takes 5s
- health gain animation speed is determined by HypoRegenerationRate in ONCC
- overpower health gain animation has an overpower sound of an approximate volume of 75% while overpower sound is played at 100% volume once overpower gain ended and drops again
An applied hypo gives you points equal to your full normal health health. At this point of time these points should be considered a "potential" because they aren't multiplied yet with hypo factors and can increase or decrease by additional healing and damage events.
As long as you regenerate normal health you regenerate from your potential multiplied by 0.25 (Oni 1 default).
As soon as you reach overhealth you multiply one point of potential by 1 (Oni 1 default).
Let's say your maximum health is 1000 and with overhealth you are at 2000.
Taking 2 hypos at health 750 will regenerate you 250 points of normal health and then - because reaching overhealth - you get another 1000 points. So taking 2 hypos at 750 health points will let you reach 2000.
A calculation with "potential" give you the advantage to stack hypos more easily.
UE4's progress bar has a fixed range of 0 to 1. In order to realize overpower values (float 1.0 to 2.0) we have to scale everything down to fit into a 0 to 100 percentage range.
If we think of the different colors as milestones on a road whereby each milestone is named red, yellow and so on then each two stones span their own area of interpolated colors. So for UE4's "Lerp (LineraColor)" we would will need several alpha ranges, always float values from 0 to 1.
Health and overhealth points have different densities which results in different grow speeds on the artwork.
To recreate a similar behavior we can let the normals health points fill the health bar with a speed of 1.5 while overpower is the sum of normal health * 1.5 plus health over 100% * 0.5. Since the the health bar isn't a full circle we tweaking the second factor to about 0.44.
- Anywhere from 0 to 20% health the bar is flashing to indicate damage.
- While regenerating the health bar is slowly increasing while a transparent area indicates how much the regeneration will be. That zone is color-interpolated on current health.
- Taken damage is indicated by instantly dropped health while a transparent area is melting to the new health value.
So, both - present and past/future health - use animations.
To deal with the transparent areas it might be the easiest to add another object to the health bar. In total we have then three: a frame texture, a "future/past health" material, and a "present health" material.
How to increase values over time? It looks like "timeline" don't have inputs for start and end time and timers don't work in character BP. Even loops are problematic: if they contain a delay node it is simply ignored. The solution seems to be a custom event. Once triggered it will increase or decrease the health value and call itself again if the certain conditions are still true. The animation seem smooth enough with a 0.1 second delay.
Health bar recreation
There are two options to create a mask: a computed texture (vector) or a real texture designed in a image editor.
For color changes we would need a dynamic material in any case.
In the following sub section there are the required steps to achieve a health display meter.
Basically the mask (shape) of the health display meter.
This will be used as a node inside the material.
Create Material Blueprint, Set output node to Material Domain: User interface (!), create a constant 4 Vector, right-click to turn it into a Parameter and name it Color, connect to output node
- Right-click in assets folder to create a new widget (User Interface > Widget Blueprint).
- Place an image into your Designer. This is going to be the health display meter.
- Under Appearance choose your Material.
Turning the Material in a dynamic one and setting initial color... Create variable of type "Material Instance Dynamic", create 2 vector variables (rgb red, rgb green, use 0 to 1 range). Then in In your widget goto Graph and add these nodes to your Construct and Custom event...
- Construct event -> Get HealthImage, Get Dynamic Material, set Dynamic Material variable, Get Player Character, Cast to ThirdPersonCharacter, Target MaxHealth, Set Progress (call function)
- Custom event -> Set Progress (with a float input), Get Dynamic Material, Set Vector Parameter Value (Parameter name ("Color") from your material), add lerp(linearColor) to SVPV node, add vector color variables to lerp node (here we can add more logic to recreate ONGS' <Color> tags)
Displaying the widget to screen and creating a reference ...
- In your Character BP create or add these to the BeginPlay event node:
- Get Player Controller, Create Widget (your health bar widget), Add to Viewport, set output Create Widget to a public variable (object) (use search to find widget)
Setting progress on the HUD... In Character PB ...
- event ..., get widget object variable, Set Progress (Target: var, Input: Health Percentage)
Tables and structures
- Data tables are created based on previously defined structures. So these two file types are best seen as a tandem to get things done.
- Structures can be made to provide default values so that new tables will have those after creation. This might be suitable for new ONCC.
- Structures can be nested.
- Wtih version 4.25, the editor and engine might need a few restarts before an new structure is usable by an older one.
- With version 4.26, this bug doesn't seem to appear anymore.
- Tables can be exported to csv and json whereby json would be the more human readable form for complex, nested structures.
- Exporting and examining tables help you understand how to programmatically create such structured tables.
- Tables can be imported by using the import button or drag and drop.
- Tables can be reimported by rightclicking then Reimport (remembers file location) or Reimport With New File.
- Reimports (converting raw data files to assets) can be done with python and probably other methods.
Any key can be used to open a menu. But when the game gets paused too then pressing the same key again cannot close the menu.
You need to set the key's property "Execute when Paused" to true.
Also, in theory a "Flipflop" node would work but has proven unreliable. Instead, a reference to the menu instead, an "Is Visible" and a "Branch" (if) node will do the trick. Additionally, an Is Valid node is placed to catch an error when the reference is used for the very first time.
We can capture the mouse within the game application if we place an "Set Input Mode Game And UI" node with "Lock Always" option.
Button and section images
Buttons have 4 styles: Normal, Hovered, Pressed, Disabled.
For now lets use only 2 textures: one for Normal, one for Hovered and Pressed.
The Disabled style just uses a tint color on the Normal texture with an RGBA of 000000FF.
Buttons can be disabled via Set Is Enabled (Target is Widget). You can get button references from the Graph window.
When you place a Grid Panel into the widget you can define columns and rows. Buttons and other elements can adapt to their given space.
You probably want to set button texture property Draw As to Image.
For standalone images Draw As Border can be used if the image is big enough. This will work like Oni's XML:PSpc where only specific sections of the image are scaled.
For extreme different resolutions we might need to bind different textures so Draw As Border won't produce empty spaces in the center.
For now I see 3 major flaws with text items:
- no richtext support (you are limited to one font color, unless you use a C++ plugin)
- text warping is not accurately working
- space for an image has to be planned in advance
Text vs text box
Because of the multiline feature I rushed to use a text box for static text.
Reminder: While "Text" is for static text, "Text box" is for letting the user type his own text.
It turns out the font color of "Text box" cannot be changed at runtime and a simple "Text" object does also has the multi-line feature.
Dynamic text content
We can either use
- A) the "Set Text" node with a reference to the object or
- B) bind the object to a text variable or function, making the code a bit shorter
// ------------------------------------ move this to Pause Menu section at a later time
When you insert text you have to compile (F7) before you see the result.
Text pages can be grouped in the Designer with a Widget Switcher. However, each text should have an Overlay as parent so they aren't all shown simultaneously.
-- WidgetSwitcher (master section, e.g. for "Main content") +-- WidgetSwitcher (section, e.g. for powerup items) +-- Overlay +-- Text (e.g. hypospray text) +-- Image (e.g. hypospray icon) -- WidgetSwitcher (master section, e.g. for "hint text") +-- WidgetSwitcher (section, e.g. for powerup item hints) +-- Overlay +-- Text (e.g. hypospray hint text)
Count direct children of WidgetSwitcher by using the its reference connected to a Get Num Widget node.
It's possibly a good idea to count all sections during the construction event and save the results in an array via Make Array node.
In theory we could create many copies of Overlay nodes. But doing so and future update would mean more work than necessary. Instead a single overlay with its children can be do the job by assigning data to them from a data table.
Variables that track active pages are destroyed as the widget is closed. If we want to resume reading after reopening a menu the variable values should have been copied to somewhere else. If we put them into character variable then we can easily restore those values even after a level loading.
HTF do I? Import a CSV File into a Data Table ( UE4 )
- if you need a free spreadsheet program you can download Calc, it is part of LibreOffice, or use Google Sheets
- create a csv file with the first cell of first column (A1) being empty and the other lower cells (A2, A3, ...) to be unique in name
- Calc: save file, check "edit filter settings", save csv with comma as field delimiter
- in ue4 goto "Add New" > Blueprints > Structure
- open that Struct and add for all columns (except for A) a variable, assign a type for each
- be sure Struct's variables have the same name as the csv column headers
- import the csv and assign the Struc to it
- data tabes can be updated by reimporting the csv
for now diary (DPge) will have this structure
- levelNumber = Integer
- pageNumber = Integer
- isLearnedMove = Integer
- caption = String
- image = Texture 2D
- diaryText = String
Stuff that might be useful later...
Then again maybe this will do: https://www.youtube.com/watch?v=e8MPDnVX1h4
If not I will try to just use one text field for diaryText, etc.
If textures aren't found in the data table, open them once from the asset browser. It seems there's a bug which prevents new textures from being registered properly until they were accessed for the first time.
Objective pages are level specific.
Optional small image.
Main and hint section holds green text (todo) and red text (done).
Main section: Small image plus orange text.
- Name .............. Function
Hint section: orange text.
Main section: Small image plus orange text.
- Attribute .......... value
Hint section: orange text.
Diary pages are global -- in a manner that the latest date would get you all other diary pages as well.
- "New move" page: main window for orange caption and a big image. Explanation in the hint section, white text.
- "Regular diary" page: main section only, white text.
Green text for main section and explanation. Hint section not shown. "Controls" Action .............. Mouse/Key
And explanation overlaying the UI.
Every key can be used in blueprints. In order to allow key remapping by the player keys should be mapped in the setting instead of using detecting their real key stroke.
Goto Project Settings > Engine > Input.
Name your action or axis, then bind a key to it.
Requirements: typically a variable in the Character BP. For example you can set "IsPickingUp?" to true with a "Q" button "Pressed" action and setting it to false with the "Released" action.
Then in Character Animation BP > EventGraph there should be a "Event Blueprint Update Animation". There you retrieve "IsPickingUp?" and save it in a local variable. You can retrieve variables from other blueprints by getting the object holding them, passing it to a cast node, pulling a wire out of the right side and search for it in the popping up context menu.
Then in Character Animation BP > Anim Graph > Default State Machine you pull out from Idle/Run a wire and create a new state.
In the transition rule for Idle to Pickup create two nodes: The local variable IsPickingUp and the "Result - Can Enter Transition".
In the transition rule for Pickup to Idle create three nodes: The local variable IsPickingUp, a boolean "NOT" and the "Result - Can Enter Transition".
Now the character plays the animation as long as you press the key. If you want to play the full animation just by pressing the button firmly you have to use a slightly different setup. Use these three nodes instead: "Time Remaining (ratio)"(Animation name), float > float node, Result node.
The float 0 to 1 means how much of the animation will play before it is aborted if the key wasn't longer pressed. In this case, the lower check float the more of the animation is played. Use a number greater than 0 like 0.001
To disable other input you have to create another variable in the Character BP with is also retrieved in the Character Animation BP by casting.
In this example it was named "Movement Allowed". The event that sets and resets the variable was created in the Animation.
Every animation can have Notifies events. Right-click notifies track to add a new custom notify.
Can be created in Photoshop (3D) and Blender.
Level Blueprint, right-click to search for "Keyboard Event V", add that node, drag the "Pressed" slot to the backgound to create another node: "Print String". Compile. Close.
Play Level, hit "V". "Hello" appears on the screen.
Use you imagination: This could be used to toggle visibility of developer stuff. In connection with a HUD you could create the equivalent of "chr_debug_characters = 1"
Cooking the game
On Windows, run the game editor with administration rights.
Then go: File > Package Project > Windows > Windows (64-bit).
Choose an output folder.
You can now play the game.
Exit the game
Create two nodes in level blueprints: "input keyboard escape" and "quit game". Connect the execution slots.