Bridging Different Worlds: How Cavylabs Meshed React Native with Cocos Creator
By Luis Daher, Co-Founder & Studio Head at Cavylabs
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:
Choose i
or a
and 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
Text
component (to display what we will receive from the Game); - a
Button
, to send a message to the game.
And it will look like this:
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.
Make sure that the “preview in browser” mode is selected, as follows:
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.
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 theWebview
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:
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:
After clicking the “Send json to cocos” button:
And, after clicking on “Send JSON to React Native” button:
We did it!
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)!