Bridging Different Worlds: How Cavylabs Meshed React Native with Cocos Creator

By Luis Daher, Co-Founder & Studio Head at Cavylabs

Luis Daher
Cavylabs
7 min readDec 15, 2023

--

(photo by Fanjianhua)

Introduction

Greetings! In this article, I will be sharing my experience of creating a bridge between a React Native app and a game made with Cocos Creator, rendered in a Webview.

The Challenge

Our mission is to establish communication between the game and the rest of the app. This is a bit tricky, because it involves finding a way to send and receive messages from both of these entities.

The communication between the game and app enables, of course, data exchange. That might be useful, for instance:

  • To help the app sending a context to the game;
  • For the game to send the results back to the app (so that the app can process it however it needs);

The Motivation

The primary goal of integrating more interactive games into apps is to enhance user engagement through gamification, offering a richer and more immersive gaming experience. This approach aims to boost user retention rates within the app. Cocos Creator has proven to be the perfect tool, considering it’s able to generate HTML5 lightweight yet well-polished games.

It is also worth saying that companies such as Alibaba and Shopee already use this approach, having very positive outcomes. Facebook Instant Gaming and Tiktok Games also use the same architecture. That led to high traffic and high revenue.

Step 1: Crafting the React Native App with WebView

Let’s start with the basics. We rolled up our sleeves and began with creating a React Native sample project. Here’s where we add the secret sauce — the WebView component. This isn’t your average WebView; it’s a gateway into our Cocos game universe.

We have followed this tutorial to create a React Native app and its dependencies. After installing all the dependencies (OpenJDK, NodeJS), we’ve proceeded to create the app by running the following commands:

# this creates the React Native Project
npx react-native@latest init RnWebviewCocos

# cd into the created folder
cd RnWebviewCocos

# install the react-native-webview lib (yarn or expo could have been used)
npm i react-native-webview

In order to test it:

# Start Metro Server (`yarn start` can be used as well)
npm start

And that should appear:

Metro’s CLI options

Choose i or aand it will spawn the emulator you chose (after some time… I’ll advise you to grab some ☕️ or 🍵, due to 🐌 reasons).

(Pro tip: if you run into issues or errors, run `npx react-native doctor`. It pops up an interactive CLI where you’re able to fix whatever problems your React Native installation is facing).

Our App.tsx will only show our component:

import React from 'react';
import { View, StyleSheet, SafeAreaView } from 'react-native';
import CocosWebviewComponent from './CocosWebviewComponent'; // Import your custom component

const App = () => {
return (
<SafeAreaView style={styles.container}>
{/* Embed your custom component */}
<CocosWebviewComponent />
</SafeAreaView>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
},
});

export default App;

And our CocosWebviewComponent.tsx file:

import React, { Component } from 'react';
import { View, Text, Button } from 'react-native';
import { WebView } from 'react-native-webview';

interface CocosWebviewComponentState {
webViewUrl: string;
}

class CocosWebviewComponent extends Component<{}, CocosWebviewComponentState> {
constructor(props: {}) {
super(props);
this.state = {
webViewUrl: 'https://google.com', // Replace with your WebView URL
};
}

render() {
return (
<View style={{ flex: 1 }}>
{/* WebView */}
<WebView source={{ uri: this.state.webViewUrl }} style={{ flex: 1 }} />

{/* Label */}
<Text style={{ fontSize: 18, textAlign: 'center' }}>
Message will appear here
</Text>

{/* Button */}
<Button
title="Send JSON to Cocos"
onPress={() => {
// Handle button click here
alert('JSON Sent!');
}}
/>
</View>
);
}
}

export default CocosWebviewComponent;

So far, it contains:

  • a Webview (pointing to google.com, as a test);
  • a Textcomponent (to display what we will receive from the Game);
  • a Button, to send a message to the game.

And it will look like this:

Our Beautiful WebView App

Step 2: Creating a Simple Cocos Web Mobile Game

With our React Native app set up, we turned our attention to Cocos Creator. We have created a “Hello World” Cocos Creator web mobile project, for that. We have followed both “Hello World” and “Publish to Web” tutorials from the official documentation.

We have added a Button and a Text (and a black background, because… aesthetics🎨). There’s no code yet, so currently it’s more as a check if everything is working altogether.

The second simplest “Game” of the week, according to (somewhat trusted) sources

Make sure that the “preview in browser” mode is selected, as follows:

Previewing in editor is a very good option for games that don’t need this type of communication

As soon as you hit the play button, a browser will open, pointing to http://localhost:7456. Cocos Creator creates a test server, which we can use in our example. However, in order to React Native to access “localhost” domains, you will need to use a tool such as ngrok . You can check their Quickstart guide here.

Having ngrok installed and set up, run the following command in the terminal:

ngrok http 7456

Copy the “Forwarding” url that appears in the instructions and add it to your React Native Webview URL component, like so:

Et voilà! Now, let’s make these guys talk with each other.

Magic!

Step 3: The Enchanting Message Exchange

Now for the magic — enabling the two-way communication between the React Native app and the Cocos game. We implemented a feature where a button in the React Native app sends a message to the Cocos game, and vice versa.

The React Component needed the following changes:

  • to add an onMessage listener to the Webview component;
  • This listener should parse the message and assign the message to the Text component;
  • to bind the Webview ref to a component variable;
  • a method that sends the message when the Button gets clicked;

It will, then, look like this:

import React, { Component } from 'react';
import { View, Text, Button } from 'react-native';
import { WebView, WebViewMessageEvent } from 'react-native-webview';

interface CocosWebviewComponentState {
webViewUrl: string;
message: string;
}

class CocosWebviewComponent extends Component<{}, CocosWebviewComponentState> {
constructor(props: {}) {
super(props);
this.state = {
webViewUrl: '<your-url>', // Replace with your WebView URL
message: 'Message will appear here',
};
}

webViewRef: WebView | null = null;

handleOnMessage = async (event: WebViewMessageEvent): Promise<void> => {
const messageReceived = JSON.parse(event.nativeEvent.data);
this.setState({ message: messageReceived.message });
};

sendMessageToGame = (): void => {
this.webViewRef?.postMessage(JSON.stringify({ message: 'react2cocos' }));
};

render() {
return (
<View style={{ flex: 1 }}>
{/* WebView */}
<WebView
source={{ uri: this.state.webViewUrl }}
style={{ flex: 1 }}
onMessage={this.handleOnMessage}
ref={(ref) => (this.webViewRef = ref)}
/>

{/* Label */}
<Text style={{ fontSize: 18, textAlign: 'center' }}>
{this.state.message}
</Text>

{/* Button */}
<Button
title="Send JSON to Cocos"
onPress={() => {
// Handle button click here
this.sendMessageToGame();
}}
/>
</View>
);
}
}

export default CocosWebviewComponent;

Going back to the Cocos project, we will need to create a Component that:

  • Has a public method to send the message (this should be attached to our Button event;
  • Has a listener to the message event, added when the Component is loaded;
  • This listener should have a callback method attached to it, which parses the message and assigns it to the RichText Component;

The Component should look like this:

import { _decorator, Component, Node, RichText } from 'cc'
const { ccclass, property } = _decorator

@ccclass('MessageHandler')
export default class MessageHandler extends Component {
@property({ type: RichText })
richText: RichText

onLoad () {
/*
* depending on the OS (Android or iOS), 'document' or 'window' are used.
* So these lines are added altogether for redundancy.
*
* These lines register the callback method as a listener for whenever
* an event called 'message' is emitted.
*/
document.addEventListener('message', this.onMessageReceived)
window.addEventListener('message', this.onMessageReceived)
}

onMessageReceived = (event: any) => {
// This parses the JSON string and sends the message to the RichText component
const message = JSON.parse(event.data)
this.richText.string = message.message
}

sendMessage () {
// this transforms the JSON into a string...
const jsonString = JSON.stringify({ message: 'cocos2react' })

//...and posts it as a 'message' event (some extra redundancy for different OSes as well)
if (window.ReactNativeWebView && window.ReactNativeWebView.postMessage) {
window.ReactNativeWebView.postMessage(jsonString)
} else {
window.postMessage(jsonString)
}
}
}

Your hierarchy should look somewhat like this:

The same as before, with our new component with the RichText attached to it.

Let’s test it!

Well, let’s turn on the Expo and Cocos servers, attach Ngrok again and see what happens.

The app should look like that, at the start:

That took A LOT of trial and error attempts…

After clicking the “Send json to cocos” button:

…which I hope you can avoid by following this article…

And, after clicking on “Send JSON to React Native” button:

…so that you can reach THIS! :D

We did it!

Oh, the joy :D (I am deeply sorry if you happen to be Italian, though…)

The Takeaway

Through this project, we’ve learned that technology, like art, thrives on creativity and collaboration. Bringing together React Native and Cocos Creator was a journey filled with learning, coffee, and the occasional eureka moment.

Conclusion

At Cavylabs, we believe in breaking barriers and building bridges in the tech world. This adventure is just one example of how we mix different technologies to create something unique and impactful. If you’re intrigued by this tech fusion or have stories of your own, we’re all ears (and keyboards)!

--

--